Тип возврата C ++, когда я не знаю, является ли он временным - PullRequest
12 голосов
/ 28 июня 2011

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

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

Вот связанный вопрос , но под другим углом.

Ответы [ 4 ]

9 голосов
/ 28 июня 2011

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

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, а другой * float.

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

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

3 голосов
/ 28 июня 2011

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

1 голос
/ 01 июля 2011

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

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

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

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

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

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

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

1 голос
/ 28 июня 2011

Вот один из способов:

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: изменение форматирования кода

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