Вернуть константную ссылку или копию в функции получения? - PullRequest
40 голосов
/ 02 февраля 2010

Что лучше по умолчанию , чтобы вернуть копию (1) или ссылку (2) из ​​функции получателя?

class foo {
public:
    std::string str () { // (1)
        return str_;
    }

    const std::string& str () { // (2)
        return str_;
    }

private:
    std::string str_;
};

Я знаю, 2) может быть быстрее, но не обязательно из-за (N) RVO. 1) безопаснее в отношении свисающих ссылок, но объект, вероятно, устареет, или ссылка никогда не будет сохранена.

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

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

Ответы [ 8 ]

21 голосов
/ 02 февраля 2010

Ну, это действительно зависит от того, что вы ожидаете от поведения по умолчанию.

Ожидаете ли вы, что вызывающий абонент увидит изменения, внесенные в str_ без ведома (какое слово!) Им? Затем вам нужно вернуть ссылку. Может быть хорошо, если у вас есть переоцененный элемент данных и вы вернете его.

Если вы ожидаете, что звонящий получит копию, сделайте 1).

18 голосов
/ 02 февраля 2010

Мое эмпирическое правило - возвращать копию для простых базовых типов данных, таких как int, string и т. Д. Для немного более сложных структур, где копирование может быть более дорогим (как, например, упомянутый вами вектор), я предпочитаю возвращать const-ссылку.

10 голосов
/ 02 февраля 2010

Компилятор не сможет выполнить (N) RVO в этом случае. (Именованная) оптимизация возвращаемого значения - это оптимизация, при которой компилятор создает функцию авто-переменных вместо возвращаемого значения, чтобы избежать необходимости копировать:

std::string f()
{
   std::string result;
   //...
   return result;
}

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

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

7 голосов
/ 04 февраля 2010

Я возвращаю ссылку, потому что мне кажется, что строка не «дешевая для копирования». Это сложный тип данных с динамическим управлением памятью и все такое.

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

string s = obj.str();

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

  • Умные указатели
  • итераторы
  • Все не классовые типы.
3 голосов
/ 02 февраля 2010

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

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

1 голос
/ 02 февраля 2010

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

const MyClass & ref = container.GetAt( 1234 ); // need only reference
MyClass copy = container.GetAt( 1234 ); // get writable copy 

На самом деле это довольно просто, не так ли?

0 голосов
/ 02 февраля 2010

Единственная проблема, с которой я сталкиваюсь при возврате const-ссылки, что я обычно делаю для неосновных типов, заключается в том, что ничто не мешает вызывающей стороне удалить «const» и затем изменить значение. *

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

0 голосов
/ 02 февраля 2010
  1. , если это маленький базовый тип - примитивы, такие как int и long, и их обертки и другие базовые вещи, такие как 'Point' - вернуть копию

  2. , если его строка или любой другой сложный тип - возвращает ссылку.

...