Подтвердить что ты не робот

Как вернуть интеллектуальные указатели (shared_ptr), по ссылке или по значению?

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

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

Две возможные подсказки:

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

  • Код вырос на 150%
  • Нам пришлось отказаться от const -ness, потому что были мутирующие имена.
  • Как функциональные программисты любят напоминать нам, мутация делает код более сложным для объяснения, подрывая ссылочную прозрачность и эквациональные рассуждения.
  • У нас больше нет строгой семантики значений для имен.

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

Ответ 2

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

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

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