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

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

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

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

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

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


person Vincenzo Pii    schedule 17.05.2012    source источник


Ответы (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 string, каждая копия может потребовать до N + 1 выделения памяти и целого ряда недружественных к кешу обращений к данным> по мере копирования содержимого строки.

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

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

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

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

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

person In silico    schedule 17.05.2012
comment
Я не знаю, могу ли я сказать, что RVO делает вопрос спорным, поскольку возврат по ссылке решительно делает невозможным RVO. - person Edward Strange; 18.05.2012
comment
@CrazyEddie: Верно, это одна из причин, по которой я рекомендую возвращать OP по значению. - person In silico; 18.05.2012
comment
Правило RVO, разрешенное стандартом, превосходит правила относительно отношений синхронизации / происходит раньше, гарантированные стандартом? - person edA-qa mort-ora-y; 18.05.2012
comment
@ edA-qa mort-ora-y: RVO явно разрешен, даже если он имеет побочные эффекты. Например, если у вас есть оператор cout << "Hello World!"; в конструкторе по умолчанию и в конструкторе копирования, вы не увидите два Hello World!, когда действует RVO. Однако это не должно быть проблемой для правильно спроектированных интеллектуальных указателей, даже w.r.t. синхронизация. - person In silico; 18.05.2012
comment
Что, если shared_ptr является параметром, передается по значению или константной ссылке? - person Alfred; 28.01.2021

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

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

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

person San Jacinto    schedule 17.05.2012