Как вернуть смарт-указатели (общий ptr), по ссылке или по значению?


Допустим у меня есть класс с методом, который возвращает shared_ptr.

каковы возможные преимущества и недостатки возвращая его по ссылке или по значению?

две возможные ключи:

  • раннее уничтожение объекта. если я верну shared_ptr по ссылке (const) счетчик ссылок не увеличивается, поэтому я несу риск удаления объекта, когда он выходит из области действия в другом контексте (например, в другом потоке). Есть это правильно? Что делать, если среда является однопоточной, может ли эта ситуация произойти?
  • стоимость. Pass-by-value, конечно, не бесплатно. Стоит ли избегать его, когда это возможно?

спасибо всем.

2 68

2 ответа:

возвращает интеллектуальные указатели по значению.

как вы уже сказали, если вы вернете его по ссылке, вы не будете правильно увеличивать количество ссылок, что открывает риск удаления чего-то в неподходящее время. Одного этого должно быть достаточно, чтобы не возвращаться по ссылке. Интерфейсы должны быть надежными.

проблема стоимости в настоящее время спорна благодаря оптимизация возвращаемого значения (RVO), так что вы не понесете последовательность инкремент-инкремент-декремент или что-то в этом роде в современных компиляторах. Так что лучший способ вернуть shared_ptr это просто вернуть по значению:

shared_ptr<T> Foo()
{
    return shared_ptr<T>(/* acquire something */);
};

это мертвая очевидная возможность RVO для современных компиляторов C++. Я точно знаю, что компиляторы Visual C++ реализуют RVO, даже когда все оптимизации отключены. И с семантикой перемещения C++11 эта проблема еще менее актуальна. (Но единственный способ быть уверенным-это профилировать и экспериментировать.)

если вы все еще не убеждены, Дэйв Абрахамс и статьи что делает аргумент для возврата по значению. Я воспроизвожу фрагмент Здесь; я настоятельно рекомендую вам прочитать всю статью:

будьте честны: как следующий код заставляет вас чувствовать себя?

std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();

честно говоря, хотя я должен знать лучше, это заставляет меня нервничать. В принципе, когда get_names() возвращается, мы должны скопировать vector на strings. Затем нам нужно скопировать его снова когда мы инициализируем names, и нам нужно уничтожить первую копию. Если есть N strings в векторе, каждая копия может потребоваться до N + 1 выделения памяти и целый ряд кэш-недружественных доступов к данным > при копировании содержимого строки.

вместо того, чтобы противостоять такого рода беспокойству, я часто возвращался на pass-by-reference, чтобы избежать ненужные копии:

get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );

к сожалению, этот подход далек от идеала.

  • код вырос на 150%
  • мы должны были упасть const-Несс, потому что мы мутируем имена.
  • у нас больше нет строгой семантики значений для имен.

но действительно ли необходимо испортить наш код таким образом, чтобы получить эффективность? К счастью, ответ оказывается нет (и особенно, если вы используете C++0x).

о любой умный указатель (не только shared_ptr), я не думаю, что когда-либо допустимо возвращать ссылку на один, и я бы очень не решался передавать их по ссылке или необработанному указателю. Зачем? Потому что вы не можете быть уверены, что он не будет мелко скопирован через ссылку позже. Ваш первый пункт определяет причину, по которой это должно быть проблемой. Это может произойти даже в однопоточной среде. Вам не требуется параллельный доступ к данным, чтобы поставить плохую копию семантика в ваших программах. Вы действительно не контролируете, что ваши пользователи делают с указателем, как только вы его передаете, поэтому не поощряйте неправильное использование, давая вашим пользователям API достаточно веревки, чтобы повеситься.

во-вторых, посмотрите на реализации умного указателя, если это возможно. Строительство и разрушение должны быть чертовски близки к незначительным. Если эти накладные расходы не приемлемы, то не используйте смарт-указатель! Но помимо этого, Вам также нужно будет изучить архитектуру параллелизма, которую вы создали got, потому что взаимоисключающий доступ к механизму, который отслеживает использование указателя, замедлит вас больше, чем просто построение объекта shared_ptr.

Edit, 3 года спустя: с появлением более современных функций в C++ я бы изменил свой ответ, чтобы быть более приемлемым для случаев, когда вы просто написали лямбду, которая никогда не живет за пределами области действия вызывающей функции и не копируется где-то еще. Здесь, Если вы хотите сохранить минимальные накладные расходы копирование общего указателя было бы справедливым и безопасным. Зачем? Потому что вы можете гарантировать, что ссылка никогда не будет неправильно использоваться.