Почему shared_ptr стоит передавать по ссылке

Доброе утро!

Я уже затрагивал тему умных указателей, когда говорил про boost::offset_ptr. В сегодняшней краткой заметке я хочу написать, почему как стандартный, так и бустовый smart_ptr'ы стоит передавать по ссылке в ситуациях, когда это возможно.

Рассмотрим следующий код:

#include <chrono>
#include <iostream>
#include <memory>

#include <boost/smart_ptr/shared_ptr.hpp>

using namespace std::chrono;

struct sometype {
    int x;
};

void stdspbyval(std::shared_ptr<sometype> p) {
    (void)p;
}

void stdspbyref(const std::shared_ptr<sometype> &p) {
    (void)p;
}

void boostspbyval(boost::shared_ptr<sometype> p) {
    (void)p;
}

void boostspbyref(const boost::shared_ptr<sometype> &p) {
    (void)p;
}

int main(int c, char **v) {
    high_resolution_clock::time_point t1, t2;

    std::shared_ptr<sometype> p = std::make_shared<sometype>();
    t1 = high_resolution_clock::now();
    for (int i = 0; i < 10000000; ++i) {
        stdspbyval(p);
    }
    t2 = high_resolution_clock::now();
    auto duration = duration_cast<microseconds>(t2 - t1).count();
    std::cerr << "stdspbyval:\t" << duration << std::endl;

    t1 = high_resolution_clock::now();
    for (int i = 0; i < 10000000; ++i) {
        stdspbyref(p);
    }
    t2 = high_resolution_clock::now();
    duration = duration_cast<microseconds>(t2 - t1).count();
    std::cerr << "stdspbyref:\t" << duration << std::endl;

    boost::shared_ptr<sometype> pp{new sometype()};
    t1 = high_resolution_clock::now();
    for (int i = 0; i < 10000000; ++i) {
        boostspbyval(pp);
    }
    t2 = high_resolution_clock::now();
    duration = duration_cast<microseconds>(t2 - t1).count();
    std::cerr << "boostspbyval:\t" << duration << std::endl;

    t1 = high_resolution_clock::now();
    for (int i = 0; i < 10000000; ++i) {
        boostspbyref(pp);
    }
    t2 = high_resolution_clock::now();
    duration = duration_cast<microseconds>(t2 - t1).count();
    std::cerr << "boostspbyref:\t" << duration << std::endl;

    (void)c;(void)v;
    return 0;
}

Вызываются функции, которые ничего не делают. Функции принимают соответствующие указатели, по значению и по ссылке. Пример вывода:

$ ./pointers
stdspbyval:     643491
stdspbyref:     27704
boostspbyval:   394144
boostspbyref:   28561

Видим явное отличие в порядках времени выполнения между аналогичными указателями при передаче по ссылке и значению. Данное различие возникает по причине того, что счётчик ссылок в данных указателях — атомарная переменная. Это ведёт к тому, что при большом числе копирований может проседать производительность.

Вывод: если есть возможность, стоит отдать предпочтение передаче умного указателя по ссылке, а не по значению.