Возврат по ссылке переменной-члена КОГДА-ЛИБО приемлем?

Предположим, у меня есть:

class Metadata {
    // stores expensive-to-copy data, provides complex interface to access/modify
}

class SomeObject
public:
    Metadata& GetMetadata() { return mMetadata; }
private:
    Metadata mMetadata;
}

boost::shared_ptr<SomeObject> obj = ...;
obj->GetMetadata().SetTitle("foo");
obj->GetMetadata().GetTitle();

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

  1. Я не хочу возвращать копию, так как хочу изменить оригинал (и даже если это было r/o, копирование слишком дорого).
  2. Я не хочу возвращать метаданные* или что-то подобное, так как я не хочу поощрять сохранение или передачу указателя.
  3. Я не хочу загрязнять интерфейс SomeObject ненужными вызовами оболочки для метаданных.

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

Мне пришло в голову, что я мог бы ввести какой-то некопируемый тип указателя и вернуть его, но это пахнет забавно / кажется излишним. Я мог бы заставить SomeObject владеть boost::shared_ptr, но это действительно не очень хорошо соответствует семантике, которую я имел в виду (в основном: если вы изменяете сейчас, измените оригинал - если вы читаете позже/сохраняете, сделайте копию и следите за собой).

Есть ли лучший шаблон для использования здесь? Буду ли я стрелять себе в ногу каким-то образом, которого я не вижу?


person scztt    schedule 23.10.2014    source источник
comment
Кто тебе сказал, что это плохо? Возврат ссылок на члены данных класса — распространенный и полезный метод программирования.   -  person Kerrek SB    schedule 24.10.2014
comment
Я не вижу проблемы с возвратом неконстантных ссылок. Хотя может быть проще просто сделать переменную-член общедоступной.   -  person Galik    schedule 24.10.2014
comment
Учитывая, что Metadata уже инкапсулирует необработанные данные, также может быть совершенно нормально просто сделать mMetadata общедоступным членом SomeObject (желательно с лучшим именем) и использовать его напрямую, вместо того, чтобы проходить через GetMetadata() (который, похоже, ничего не дает). в таком случае).   -  person Jerry Coffin    schedule 24.10.2014
comment
@KerrekSB Скотт Мейерс сделал (Эффективный C ++, пункт 28). Хотя он говорит, что есть исключения, такие как вектор.   -  person Chris Drew    schedule 24.10.2014
comment
Вызовы оболочки для Metadata могут быть ненужными, но они не должны быть неуместными, иначе Metadata не принадлежит SomeObject.   -  person Chris Drew    schedule 24.10.2014
comment
@KerrekSB - Моя интуиция и последующие споры с самим собой подсказали мне, что у меня есть правильный ответ. Я был удивлен, когда огляделся, что эта практика довольно повсеместно не рекомендуется (по крайней мере, в общих случаях - поиск по SO в значительной степени дает просто не ответы), и мне было любопытно, есть ли альтернативные шаблоны проектирования или ловушки, которых я не знал подумал о.   -  person scztt    schedule 26.10.2014
comment
Я все еще заинтригован идеей возврата некопируемого, неконструируемого указателя в качестве альтернативы. Объект, подобный указателю, будет немного более независимым от изменений времени жизни объекта в будущем, чем &, и позволит, например. добавление поведения, похожего на scoped_lock, если мне нужна безопасность потоков (что я почти сделал в рассматриваемом случае).   -  person scztt    schedule 26.10.2014
comment
@scztt: пожизненный агностик звучит как худший из миров. Во всяком случае, вы хотите прямо противоположное: пожизненные гарантии как часть системы типов, насколько это возможно.   -  person Kerrek SB    schedule 26.10.2014


Ответы (2)


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

struct Foo
{
    X data;

    X & the_data() &   { return data; }
//                ^^^

Пока вы это делаете, вы также можете вернуть данные как rvalue, если ваш экземпляр является rvalue:

    X && the_data() && { return std::move(data); }
};
person Kerrek SB    schedule 23.10.2014
comment
@T.C.: О, да. Или std::move(*this).data :-) Более идиоматично (переместите объект, привязанный к rvalue). - person Kerrek SB; 24.10.2014
comment
Естественно, также распространен возврат по const X&, чтобы избежать ненужного копирования возвращаемых значений. - person Deduplicator; 24.10.2014
comment
TIL вам может потребоваться такое lvalue. Каждый день я узнаю что-то новое о C++. - person chbaker0; 24.10.2014
comment
Спасибо - не знал о вариантах использования l/rvalue. - person scztt; 26.10.2014

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

Это позволяет компилятору C++ (который по своей конструкции является агрессивно оптимизирующим компилятором языка) оптимизировать многие вещи, а также позволяет писать «свободный» синтаксис.

obj.foo().bar().baz();
person codenheim    schedule 23.10.2014
comment
Я не согласен. Я думаю, что у вас действительно должна быть веская причина для возврата ссылки, и возврат по значению должен быть предпочтительным. Лучшая инкапсуляция, более слабая связанность, меньшая вероятность ошибок и т. д. Однако очевидно, что есть причины возвращаться по ссылке. - person Chris Drew; 24.10.2014
comment
@ChrisDrew Если мы говорим исключительно о современном C ++ со ссылкой на rvalue и семантикой перемещения, то я согласен с вами. И уж точно на муфте, хотя внутренние типы я обычно не прячу. Какой тип объектов вы предлагаете лучше возвращать копии по умолчанию? Большая часть кода, с которым я работаю, использует множество внутренних дескрипторов или контейнеров и указателей узлов, поэтому я предпочитаю не копировать по умолчанию. - person codenheim; 24.10.2014
comment
Я бы сказал, что было бы разумным выбором по умолчанию возвращать по значению для основных типов, небольших строк, крошечных объектов, в любое время, когда вы можете получить оптимизацию возвращаемого значения и в любое время, когда вы можете двигаться. Таким образом, в основном остается случай, когда вы хотите избежать переадресации вызовов внутреннему объекту среднего и большого размера и хотите нарушить инкапсуляцию. Что может быть именно ситуацией ОП. - person Chris Drew; 24.10.2014
comment
Отредактировано. Если подумать, я согласен, что мое заявление было слишком сильным, чтобы быть полезным. На практике я не думаю, что есть какой-то подход по умолчанию, только подходящий подход. Я работаю с большим количеством толстых объектов или с вложенными контейнерами, DAG и AST, поэтому использую в основном указатели и ссылки, но мой класс, вероятно, нетипичен. - person codenheim; 24.10.2014