Подтвердить что ты не робот

Возвращаемый объект из функции

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

Сценарий 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();
//...
}

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

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

4b9b3361

Ответ 1

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

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

class Foo { ... }

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

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

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

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

Там у вас есть. С++ - странно, но полезно.


Итак, посмотрим на ваши сценарии

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

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

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

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

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

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

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


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


Ответ 2

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

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

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

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

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