Возвращаемый тип С++, когда я не знаю, временно ли он

Предположим, что Foo - довольно большая структура данных. Как написать виртуальную функцию const, которая возвращает экземпляр Foo, если я не знаю, будет ли унаследованный класс хранить экземпляр Foo внутренне; таким образом, обеспечивая возврат по ссылке. Если я не могу сохранить его внутренне, я понимаю, что я не могу вернуть ссылку const, потому что он будет временным. Это верно? Возможны два варианта:

virtual Foo foo() const { ... }
virtual Foo const & foo() const { ... }

Здесь связанный вопрос, но под другим углом.

Ответ 1

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

const Foo &a = myobj.foo();
myobj.modify_the_foo();
const Foo &b = myobj.foo();
a == b; // do you want this to be true or false?

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

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

Другая возможность состоит в том, чтобы превратить Foo в небольшой класс стиля pImpl, который ссылается на довольно большую структуру данных. Если все задействованное является неизменным, то случай "хотеть вернуть ссылку" может совместно использовать большую структуру данных между несколькими экземплярами Foo. Даже если это не так, вы можете думать о copy-on-write.

Ответ 2

Я вижу, что вы не указали С++ 0x в качестве тега, но как ссылку для всех, у кого есть ваши потребности, плюс доступ к С++ 0x, возможно, лучший способ - вернуть std::unique_ptr<>.

Ответ 3

Здесь один из способов:

struct K 
 { 
 int ii; 
 };

class I 
  { 
  virtual K &get_k(int i)=0; 
  };

class Impl : public I 
  { 
  K &get_k(int i) { kk.ii = i; return kk; } 
  K kk; 
  };

Что заставляет работать, так это то, что вы используете K kk; внутри того же объекта, что и элемент данных. Может оказаться полезным и конструктор класса Impl.

EDiT: изменение форматирования кода

Ответ 4

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

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

Если ваша главная проблема заключается в том, чтобы избежать копирования, вы можете добиться этого, сделав ваш интерфейс немного менее приятным:

virtual void foo(Foo& putReturnvalueHere) const { ... }

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

virtual void foo(Foo* unconstructedFoo) const { ... }

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