Возврат объекта из функции - PullRequest
19 голосов
/ 11 апреля 2010

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

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

const Foo SomeClass::GetFoo() {
 return Foo(); 
}

вызывается как:

someMethod() {
 const Foo& l_Foo = someClassPInstance->GetFoo();
//...
}

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

void SomeClass::GetFoo(Foo& a_Foo_ref) {
     a_Foo_ref = Foo(); 
    }

вызывается как:

someMethod() {
 Foo l_Foo;
 someClassPInstance->GetFoo(l_Foo);
//...
}

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

Foo l_Foo

Сценарий C:

Foo SomeClass::GetFoo() {
 return Foo(); 
}

вызывается как:

someMethod() {
 Foo l_Foo = someClassPInstance->GetFoo();
//...
}

Я думаю, что это не рекомендуемый подход, так как это повлечет за собой создание дополнительных временных.

Что ты думаешь? Кроме того, вы рекомендуете лучший способ справиться с этим вместо

Ответы [ 2 ]

16 голосов
/ 11 апреля 2010

Во-первых, давайте посмотрим на вещи, которые вступают в игру здесь:

(a) Продление срока действия временного , когда он используется для инициализации ссылки - об этом я узнал в этой публикации Андрея Анександреску. Опять же, это странно, но полезно:

class Foo { ... }

Foo GetFoo() { return Foo(); }  // returning temporary

void UseGetFoo()
{
   Foo const & foo = GetFoo();
   // ... rock'n'roll ...
   foo.StillHere();
}

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

(b) Оптимизация возвращаемого значения - ( wikipedia ) - две копии локально -> возвращаемое значение -> локально может быть опущено при определенных обстоятельствах , Это удивительное правило, поскольку оно позволяет компилятору изменять наблюдаемое поведение, но полезно.

Вот, пожалуйста. C ++ - странно, но полезно.


Итак, глядя на ваши сценарии

Сценарий A: вы возвращаете временный объект и связываете его со ссылкой - срок жизни временного объекта увеличивается до времени жизни l_Foo.

Обратите внимание, что это не будет работать, если GetFoo вернет ссылку, а не временную.

Сценарий B: Работает, за исключением того, что он вызывает цикл Construct-Construct-Copy-Cycle (который может быть намного дороже, чем одиночная конструкция), и проблема, о которой вы упоминаете требующий конструктор по умолчанию.

Я бы не использовал этот шаблон для создания объекта - только для изменения существующего.

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

Rvalue ссылки в C ++ 0x позволяет Foo реализовать конструктор похищения ресурсов, который не только гарантирует подавление копий, но и пригодится в других сценариях.

(Я сомневаюсь, что есть компилятор, который реализует ссылки на rvalue, но не RVO. Однако есть сценарии, в которых RVO не может войти.)


Подобный вопрос требует упоминания умных указателей, таких как shared_ptr и unique_ptr (последний является "безопасным" auto_ptr). Они также находятся в C ++ 0x . Они предоставляют альтернативный шаблон для функций, создающих объекты.


4 голосов
/ 11 апреля 2010

Из трех сценариев число 3 - идеоматический метод, и тот, который вам, вероятно, следует использовать. Вы не будете платить за дополнительные копии, потому что компилятор может свободно использовать copy elision , чтобы избежать копирования, если это возможно.

Secnario A неверен. В итоге вы получите ссылку на временный объект, который уничтожается после завершения оператора, содержащего вызов функции. Хорошо, сценарий A не ошибается, но вы все равно должны использовать сценарий C.

Secnario B работает просто отлично, но:

Допустим, у Foo не может быть конструктора по умолчанию. Тогда как бы вы справились с этим в этой ситуации, поскольку мы больше не можем писать это: Foo l_Foo.

Foo должен иметь конструктор по умолчанию. Даже если вы не дадите его, компилятор должен за вас. Если вы объявите конструктор private, вы не сможете использовать этот вызывающий метод. Вам нужно либо сделать Foo конструкцию по умолчанию, либо использовать Secnario C.

...