Возвращаемый объект из функции
Теперь я действительно запутался в том, как и какой метод использовать для возврата объекта из функции. Я хочу получить обратную связь по решениям для данных требований.
Сценарий 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();
//...
}
Я думаю, что это не рекомендуемый подход, так как он потребует создания дополнительных временных рядов.
Как вы думаете? Кроме того, вы рекомендуете лучший способ справиться с этим?
Ответы
Ответ 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.