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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

На самом деле, другая проблема специально с возвратом строки не по ссылке, заключается в том, что 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 destructed as soon as this line executes!
jniEnv->NewStringUTF(fooCName);  // No good, fooCName was released when the temporary was deleted.

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

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

Смотрите часто задаваемые вопросы С++ lite.

Ответ 9

Коэффициенты довольно хороши, что типичное использование этой функции не будет нарушено, если вы перейдете на ссылку const.

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

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