Должны ли мы передавать shared_ptr по ссылке или по значению?

Когда функция принимает shared_ptr (из boost или С++ 11 STL), вы передаете его:

  • по ссылке: void foo(const shared_ptr<T>& p)

  • или по значению: void foo(shared_ptr<T> p)?

Я бы предпочел первый метод, потому что подозреваю, что он будет быстрее. Но стоит ли это того или есть какие-то дополнительные проблемы?

Не могли бы вы привести причины вашего выбора или, если дело, почему вы думаете, что это не имеет значения.

Ответ 1

Этот вопрос был обсужден и получен ответом Скотта, Андрея и Херба во время сессии "Спросите нас что-нибудь " в C++ и после 2011 года. Смотрите с 4:34 о производительности и правильности shared_ptr.

Короче говоря, нет смысла передавать по значению, если только цель не состоит в том, чтобы разделить владение объектом (например, между разными структурами данных или между разными потоками).

Если вы не можете переместить-оптимизировать его, как объяснено Скоттом Мейерсом в приведенном выше видео-выступлении, но это связано с реальной версией C++, которую вы можете использовать.

Основное обновление этого обсуждения произошло во время конференции GoaNative 2012 conference Интерактивная панель "Спросите нас о чем-нибудь!", которую стоит посмотреть, особенно с 22:50.

Ответ 2

Здесь Herb Sutter принимает

Guideline: не передавать интеллектуальный указатель в качестве параметра функции, если вы хотите использовать или манипулировать самим интеллектуальным указателем, например доля или передача права собственности.

Руководящий принцип: Выразите, что функция будет хранить и передавать права собственности на куча с использованием параметра shared_ptr по значению.

Руководящий принцип: используйте не-const shared_ptr & параметр только для изменения shared_ptr. Использовать const shared_ptr & как параметр, только если вы не уверены, вы не будете копировать и владеть акциями; в противном случае используйте виджет * вместо этого (или если он не может быть виден, виджет &).

Ответ 3

Лично я бы использовал ссылку const. Нет необходимости увеличивать счетчик ссылок только для уменьшения его количества для вызова функции.

Ответ 4

Перейдите по ссылке const, это быстрее. Если вам нужно его сохранить, скажите в каком-то контейнере, ref. счет будет автоматически увеличиваться с помощью операции копирования.

Ответ 5

Я запустил код ниже, один раз с foo, взяв shared_ptr на const& и снова с foo, взяв shared_ptr по значению.

void foo(const std::shared_ptr<int>& p)
{
    static int x = 0;
    *p = ++x;
}

int main()
{
    auto p = std::make_shared<int>();
    auto start = clock();
    for (int i = 0; i < 10000000; ++i)
    {
        foo(p);
    }    
    std::cout << "Took " << clock() - start << " ms" << std::endl;
}

Использование сборки VS2015, x86, на моем процессоре Intel Core 2 Quad (2,4 ГГц)

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 

Версия с копией по значению была на порядок медленнее.
Если вы вызываете функцию синхронно с текущим потоком, предпочитайте версию const&.

Ответ 6

Так как С++ 11 вы должны принимать его по значению над const & чаще, чем вы думаете.

Если вы принимаете std:: shared_ptr (а не базовый тип T), то вы делаете это, потому что хотите что-то сделать с ним.

Если вы хотите скопировать его где-то, имеет смысл взять его путем копирования, а std:: перемещать его внутри, а не принимать его с помощью const & а затем копировать его. Это связано с тем, что вы разрешаете вызывающему абоненту, в свою очередь, std:: перемещать shared_ptr при вызове вашей функции, тем самым сохраняя себе набор операций увеличения и уменьшения. Или нет. То есть, вызывающая функция может решить, нужен ли ему std:: shared_ptr после вызова функции, и в зависимости от того, движется или нет. Это невозможно, если вы проходите через const &, и поэтому предпочтительно принимать его по значению.

Конечно, если вызывающий абонент нуждается в его shared_ptr дольше (таким образом, он не может std:: переместить его), и вы не хотите создавать обычную копию в функции (скажем, вам нужен слабый указатель, или вы только иногда хотят скопировать его, в зависимости от некоторого состояния), затем const & может быть предпочтительнее.

Например, вы должны сделать

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));

над

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);

Потому что в этом случае вы всегда создаете копию внутри

Ответ 7

Не зная затрат времени на операцию копирования shared_copy, где атомарный прирост и декремент включены, я столкнулся с гораздо большей проблемой использования ЦП. Я никогда не ожидал, что атомное приращение и декремент могут стоить так дорого.

Следуя моему результату тестирования, int32 атомный приращение и декремент занимает 2 или 40 раз, чем неатомное приращение и декремент. Я получил его на 3GHz Core i7 с Windows 8.1. Первый результат возникает, когда не возникает никаких споров, последний, когда возникает высокая вероятность разногласий. Я помню, что атомные операции - это, наконец, блокировка на основе оборудования. Блокировка блокируется. Плохо для производительности, когда возникает конфликт.

Почувствовав это, я всегда использую byref (const shared_ptr &), чем byval (shared_ptr).

Ответ 8

shared_ptr недостаточно велик, и его конструктор \destructor не выполняет достаточную работу, чтобы получить достаточное количество накладных расходов из копии, чтобы заботиться о прохождении по ссылке vs pass by copy.