Во время рефакторинга кода я наткнулся на несколько методов получения, которые возвращают std :: string. Примерно так:
class foo
{
private:
std::string name_;
public:
std::string name()
{
return name_;
}
};
Неужто геттеру было бы лучше вернуть const std::string&? Текущий метод возвращает копию, которая не так эффективна. Вместо этого вызовет ли возврат константной ссылки какие-либо проблемы?
@Loki: ничто не говорит о том, что это будет эффективно, и на самом деле самая последняя версия C++ говорит, что, вероятно, будет. единственной оптимизацией, которая могла бы сделать этот код достаточно эффективным, была бы std :: string с подсчетом ссылок. Однако многие реализации STL не выполняют подсчет ссылок, так как на современных многоядерных процессорах управление счетчиком ссылок происходит медленнее, чем вы думаете. Так что да, возврат копии происходит намного медленнее. Ни GCC, ни Microsoft STL уже много лет не проводят подсчет ссылок на std :: string.
@seanmiddleditch: Всегда измеряйте, прежде чем предполагать (это все, что я говорю). Есть также много других оптимизаций, которые может применить компилятор: встраивание и копирование будут двумя, что снизит стоимость до нуля.
@Loki: да, это хороший совет; всегда профиль. Также хороший совет - знать разницу между «предположением» и «фактом». Я гарантирую вам, что код Роба никогда не приведет к нулевым накладным расходам в любой реализации соответствующего компилятора C++. Язык требует, чтобы конструктор копирования был вызван хотя бы один раз при возврате из foo :: name (), даже при встраивании (не влияет на вызов конструкторов), copy elision (не применимо к этому коду) и RVO ( может уменьшить количество дополнительных вызовов конструктора копирования, но не устранить их все).
@seanmiddleditch: Да, вы используете в качестве аргумента раздел 12.8 пар. 32 (укажите этот раздел при цитировании стандарта в аргументе). Приношу свои извинения за использование термина «копирование», поскольку он гораздо более конкретен, чем я ожидал. Но я не согласен с тем, что у него нулевые накладные расходы. Вы забываете, что правило As-If (1.9, пункт 1) также может быть использовано здесь для удаления любого избыточного кода, который не имеет побочных эффектов. Пожалуйста, попробуйте измерить это.
@Loki: Я сделал пытаюсь измерить его как на GCC 4.6, так и на VC 10, оба с максимальной оптимизацией, и получил те же результаты: вызывается конструктор копирования. Это «очевидный» результат, даже если принять во внимание правило as-if, поскольку конструктор копирования std :: string на самом деле имеет побочные эффекты: он либо создает полностью новый массив символов и копию строки, либо манипулирует значение счетчика ссылок (в зависимости от реализации). Заметьте, что достаточно простой микротест на имплантате с подсчетом ссылок может не дать вам «реальных» результатов, с которыми я столкнулся на GCC с первой попытки.





Я бы изменил его, чтобы он возвращал const std :: string &. Вызывающий, вероятно, все равно сделает копию результата, если вы не измените весь вызывающий код, но это не вызовет никаких проблем.
Одна потенциальная проблема возникает, если у вас есть несколько потоков, вызывающих name (). Если вы вернете ссылку, но позже измените базовое значение, значение вызывающего абонента изменится. Но существующий код в любом случае не выглядит поточно-ориентированным.
Взгляните на ответ Димы на связанную потенциальную, но маловероятную проблему.
Зависит от того, что вам нужно делать. Возможно, вы хотите, чтобы все вызывающие изменили возвращаемое значение без изменения класса. Если вы вернете ссылку на const, которая не сработает.
Конечно, следующий аргумент заключается в том, что вызывающий может сделать свою собственную копию. Но если вы знаете, как будет использоваться эта функция, и знаете, что это произойдет в любом случае, то, возможно, это сэкономит вам шаг в коде позже.
Не думаю, что это проблема. Вызывающий может просто выполнить «std :: string s = aFoo.name ()», и s будет изменяемой копией.
Вполне возможно, что вы могли бы что-то сломать, если бы вызывающий действительно хотел копию, потому что они собирались изменить оригинал и хотели сохранить его копию. Однако гораздо более вероятно, что он действительно должен просто возвращать ссылку на константу.
Самый простой способ - это попробовать, а затем проверить, работает ли он по-прежнему, при условии, что у вас есть какой-то тест, который вы можете запустить. В противном случае я бы сосредоточился на написании теста, прежде чем продолжить рефакторинг.
Единственный способ, которым это может вызвать проблему, - это если вызывающий объект сохраняет ссылку, а не копирует строку, и пытается использовать ее после уничтожения объекта. Нравится:
foo *pFoo = new foo;
const std::string &myName = pFoo->getName();
delete pFoo;
cout << myName; // error! dangling reference
Однако, поскольку ваша существующая функция возвращает копию, вы должны не нарушать какой-либо существующий код.
Редактировать: Современный C++ (то есть C++ 11 и выше) поддерживает Оптимизация возвращаемого значения, поэтому возвращение вещей по значению больше не вызывает неодобрения. По-прежнему следует помнить о возврате очень больших объектов по значению, но в большинстве случаев это должно быть нормально.
Не нарушит существующий код, но не получит преимущества в производительности Любые. Если бы проблемы было так же легко обнаружить, как в вашем примере, это было бы хорошо, но часто это может быть намного сложнее ... например, если у вас есть vector <foo> и push_back, вызывая изменение размера и т. д. Преждевременное оптимизация и т. д. и т. д.
Конечно, вы получаете преимущество в производительности. Если вы возвращаете объект, вы оплачиваете стоимость создания дополнительной копии объекта, временной, которая возвращается. Вы не платите эту стоимость, если вернете ссылку.
@Dima: вполне вероятно, что в этом случае большинство компиляторов выполнят оптимизацию возвращаемого значения именованного значения.
@ Ричард: да, я недавно читал об этом. Но это касается только случая, когда вы сразу же присваиваете возвращаемое значение чему-то, не так ли? Что делать, если возвращаемое значение передается функции? Будет ли еще оптимизировано строительство временного?
Я думаю, вы правы в том, что это применимо только в том случае, если вы упомянули
Так каков был окончательный вердикт? Лучше передать ссылку обратно или просто передать копию? Предположим, компилятор тупой, а программист должен быть конкретным и вдумчивым, как в те времена, когда мы не ленились :)
@Dale, я бы посоветовал вернуть константную ссылку.
Пункт насчет РВО вводит в заблуждение. RVO избавит вас от ненужных копий / ходов, но не избавит от создания копии члена. Если ваша строка члена - это строка размером 1 КБ, вы все равно выделите 1 КБ при возврате по значению.
Скорее всего, типичное использование этой функции не сломается, если вы перейдете на константную ссылку.
Если весь код, вызывающий эту функцию, находится под вашим контролем, просто внесите изменения и посмотрите, жалуется ли компилятор.
Обычно я возвращаю const &, если не могу. QBziZ дает пример того, где это так. Конечно, QBziZ также утверждает, что std :: string имеет семантику копирования при записи, которая сегодня редко бывает верной, поскольку COW влечет за собой много накладных расходов в многопоточной среде. Возвращая const &, вы возлагаете на вызывающего абонента ответственность за правильные действия со строкой на его конце. Но поскольку вы имеете дело с кодом, который уже используется, вам, вероятно, не следует его изменять, если профилирование не показывает, что копирование этой строки вызывает серьезные проблемы с производительностью. Затем, если вы решите его поменять, вам нужно будет тщательно протестировать, чтобы убедиться, что вы ничего не сломали. Надеюсь, другие разработчики, с которыми вы работаете, не делают схематичных вещей, как в ответе Димы.
Это имеет значение? Как только вы используете современный оптимизирующий компилятор, функции, возвращающие по значению, не будут включать копию, если это не требуется семантически.
См. По этому поводу FAQ по C++ lite.
Оптимизация возврата по значению настолько сложна, что будет использовать указатель / ссылку на исходное значение члена i.o. копии? Я не думаю, что это так. Оптимизация Rbv пропускает несколько шагов при возврате значения, но по-прежнему вызывает ctor копирования cstring, хотя и версию на месте.
Если функция возвращает копию переменной-члена класса, как в этом примере, она действительно вернет копию. Исходный член не может быть уничтожен в процессе (в отличие от функции, которая возвращается по значению и создает временный элемент для возврата, например)
Одна из проблем для возврата константной ссылки была бы, если бы пользователь закодировал что-то вроде:
const std::string & str = myObject.getSomeString() ;
При возврате std::string временный объект останется живым и будет прикреплен к 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 совместно используют память с семантикой копирования при записи, поэтому возврат по значению может быть почти таким же эффективным, как возврат по ссылке и, вам не нужно беспокоиться о проблемах времени жизни (среда выполнения делает это для вас).
Если вас беспокоит производительность, тогда сравните это (<= не могу этого особо подчеркнуть) !!! Попробуйте оба подхода и измерьте выигрыш (или его отсутствие). Если один из них лучше и вам действительно не все равно, используйте его. Если нет, то отдавайте предпочтение защите по стоимости, которую он предлагает, от проблем на протяжении всего срока службы, упомянутых другими людьми.
Вы знаете, что они говорят о предположениях ...
Вместо этого вам нужно беспокоиться о безопасности потоков при изменении несвязанных строк :)
Хех. Что дает среда выполнения, многопоточность забирает. :)
Из любопытства - реализует ли g ++ std :: string с семантикой копирования при записи?
@ user60628 COW впала в немилость (десять лет назад) см. эта статья
COW фактически запрещен в C++ 11 и более поздних версиях.
Итак, различия между возвратом копии и возвратом ссылки:
Представление: возврат ссылки может быть быстрее, а может и нет; это зависит от того, как std::string реализован вашей реализацией компилятора (как указывали другие). Но даже если вы вернете ссылку, присваивание после вызова функции обычно включает копию, как в std::string name = obj.name();.
Безопасность: возврат ссылки может вызвать или не вызвать проблемы (висячая ссылка). Если пользователи вашей функции не знают, что они делают, сохраняя ссылку как ссылку и используя ее после того, как предоставляющий объект выходит за пределы области видимости, возникает проблема.
Если вы хотите быстро и безопасно, используйте boost :: shared_ptr. Ваш объект может внутренне хранить строку как shared_ptr и возвращать shared_ptr. Таким образом, не будет происходить копирование объекта, и это всегда безопасно (если только ваши пользователи не извлекут необработанный указатель с get() и не сделают с ним что-нибудь после того, как ваш объект выйдет из области видимости).
Фактически, другая проблема конкретно с возвратом строки нет по ссылке заключается в том, что 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 могут быть особенно уязвимы для этого типа цепочки методов, который в противном случае может показаться безвредным.
Блин, хороший улов. Хотя я думаю, что C++ 11 изменил срок службы временных файлов, чтобы сделать этот код пригодным для любого совместимого компилятора.
@MarkMcKenna Это хорошо. Я немного поискал, и этот ответ, похоже, указывает на то, что вам все еще нужно быть немного осторожным, даже с C++ 11: stackoverflow.com/a/2507225/13140
Возврат ссылки на член раскрывает реализацию класса. Это могло помешать изменить класс. Может быть полезно для частных или защищенных методов, если требуется оптимизация. Что должен возвращать геттер C++
Кто сказал, что это неэффективно. Над std :: string было проделано много работы, чтобы сделать эту операцию эффективной. Есть разница между передачей ссылки и strincg, но реальная разница обычно несущественна.