Возврат константной ссылки на объект вместо копии

StackOverflow https://stackoverflow.com/questions/134731

  •  02-07-2019
  •  | 
  •  

Вопрос

Во время рефакторинга кода я наткнулся на некоторые методы получения, которые возвращают std::string.Что-то вроде этого, например:

class foo
{
private:
    std::string name_;
public:
    std::string name()
    {
        return name_;
    }
};

Конечно, геттеру было бы лучше вернуть const std::string&?Текущий метод возвращает копию, что не так эффективно.Будет ли возврат ссылки на константу вызывать какие-либо проблемы?

Это было полезно?

Решение

Единственный способ, которым это может вызвать проблему, — это если вызывающий объект сохранит ссылку, а не скопирует строку, и попытается использовать ее после уничтожения объекта.Так:

foo *pFoo = new foo;
const std::string &myName = pFoo->getName();
delete pFoo;
cout << myName;  // error! dangling reference

Однако, поскольку ваша существующая функция возвращает копию, вы не нарушите какой-либо существующий код.

Другие советы

Вообще другая проблема конкретно с возвратом строки нет по ссылке, это тот факт, что std::string обеспечивает доступ через указатель на внутреннюю const char* через c_str() метод.Это вызвало у меня многочасовую головную боль при отладке.Например, предположим, что я хочу получить имя из foo и передать его в JNI, чтобы использовать его для создания jstring для последующей передачи в Java, и это name() возвращает копию, а не ссылку.Я мог бы написать что-то вроде этого:

foo myFoo = getFoo(); // Get the foo from somewhere.
const char* fooCName = foo.name().c_str(); // Woops!  foo.name() creates a temporary that's destructed as soon as this line executes!
jniEnv->NewStringUTF(fooCName);  // No good, fooCName was released when the temporary was deleted.

Если ваш вызывающий объект будет делать такие вещи, возможно, лучше использовать какой-нибудь интеллектуальный указатель или константную ссылку или, по крайней мере, иметь неприятный заголовок предупреждающего комментария над вашим методом foo.name().Я упоминаю JNI, потому что бывшие программисты Java могут быть особенно уязвимы для такого типа цепочек методов, которые в противном случае могут показаться безобидными.

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

const std::string & str = myObject.getSomeString() ;

С std::string return, временный объект останется активным и прикрепленным к str до тех пор, пока str не выйдет из области видимости.

Но что происходит с const std::string &?Я предполагаю, что у нас будет константная ссылка на объект, который может умереть, когда его родительский объект освободит его:

MyObject * myObject = new MyObject("My String") ;
const std::string & str = myObject->getSomeString() ;
delete myObject ;
// Use str... which references a destroyed object.

Поэтому я предпочитаю возврат константной ссылки (потому что в любом случае мне удобнее отправлять ссылку, чем надеяться, что компилятор оптимизирует дополнительное временное значение), при условии, что соблюдается следующий контракт:«если вы хотите, чтобы это было за пределами существования моего объекта, они скопируют его до уничтожения моего объекта»

Некоторые реализации std::string совместно используют память с семантикой копирования при записи, поэтому возврат по значению может быть почти таким же эффективным, как возврат по ссылке. и вам не нужно беспокоиться о проблемах со сроком службы (среда выполнения делает это за вас).

Если вас беспокоит производительность, то сравните это (<= не могу это подчеркнуть) !!!Попробуйте оба подхода и измерьте выигрыш (или его отсутствие).Если один из них лучше и вас это действительно волнует, используйте его.Если нет, то отдайте предпочтение побочной стоимости, поскольку она обеспечивает защиту от жизненных проблем, упомянутых другими людьми.

Вы знаете, что говорят о предположениях...

Хорошо, итак различия между возвратом копии и возвратом ссылки:

  • Производительность:Возврат ссылки может быть быстрее, а может и не быть;это зависит от того, как std::string реализуется вашей реализацией компилятора (как указывали другие).Но даже если вы возвращаете ссылку, назначение после вызова функции обычно включает в себя копию, как в std::string name = obj.name();

  • Безопасность:Возврат ссылки может вызвать или не вызвать проблемы (висячая ссылка).Если пользователи вашей функции не знают, что они делают, сохраняя ссылку как ссылку и используя ее после того, как предоставляющий объект выходит за пределы области видимости, то возникает проблема.

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

Я бы изменил его, чтобы он возвращал const std::string&.Вызывающий объект, вероятно, в любом случае сделает копию результата, если вы не измените весь вызывающий код, но это не создаст никаких проблем.

Одна потенциальная проблема возникает, если у вас есть несколько потоков, вызывающих name().Если вы вернете ссылку, но затем измените базовое значение, то значение вызывающего объекта изменится.Но существующий код в любом случае не выглядит потокобезопасным.

Взгляните на ответ Димы о связанной с этим потенциальной, но маловероятной проблеме.

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

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

Это имеет значение?Как только вы используете современный оптимизирующий компилятор, функции, возвращающие значение по значению, не будут использовать копию, если только это не требуется семантически.

Видеть часто задаваемые вопросы по C++ Lite на этом.

Вполне вероятно, что типичное использование этой функции не сломается, если вы перейдете на константную ссылку.

Если весь код, вызывающий эту функцию, находится под вашим контролем, просто внесите изменения и посмотрите, не пожалуется ли компилятор.

Зависит от того, что вам нужно сделать.Возможно, вы хотите, чтобы весь вызывающий объект изменил возвращаемое значение, не меняя класс.Если вы вернете константную ссылку, она не сработает.

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

Обычно я возвращаю const&, если не могу.QBziZ приводит пример того, как это происходит.Конечно, QBziZ также утверждает, что std::string имеет семантику копирования при записи, что сегодня редко бывает правдой, поскольку COW требует больших накладных расходов в многопоточной среде.Возвращая const &, вы возлагаете ответственность на вызывающую сторону поступать правильно со строкой на ее конце.Но поскольку вы имеете дело с кодом, который уже используется, вам, вероятно, не следует его менять, если только профилирование не покажет, что копирование этой строки вызывает серьезные проблемы с производительностью.Затем, если вы решите его изменить, вам нужно будет тщательно проверить, чтобы убедиться, что вы ничего не сломали.Надеюсь, другие разработчики, с которыми вы работаете, не делают отрывочных вещей, как в ответе Димы.

Возврат ссылки на член раскрывает реализацию класса.Это может помешать смене класса.Может быть полезно для частных или защищенных методов, если необходима оптимизация.Что должен вернуть геттер C++?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top