I am really confused now on how and which method to use to return object from a function. I want some feedback on the solutions for the given requirements.
Scenario A:
The returned object is to be stored in a variable which need not be modified during its lifetime. Thus,
const Foo SomeClass::GetFoo() {
return Foo();
}
invoked as:
someMethod() {
const Foo& l_Foo = someClassPInstance->GetFoo();
//...
}
Scneraio B:
The returned object is to be stored in a variable which will be modified during its lifetime. Thus,
void SomeClass::GetFoo(Foo& a_Foo_ref) {
a_Foo_ref = Foo();
}
invoked as:
someMethod() {
Foo l_Foo;
someClassPInstance->GetFoo(l_Foo);
//...
}
I have one question here: Lets say that Foo cannot have a default constructor. Then how would you deal with that in this situation, since we cant write this anymore:
Foo l_Foo
Scenario C:
Foo SomeClass::GetFoo() {
return Foo();
}
invoked as:
someMethod() {
Foo l_Foo = someClassPInstance->GetFoo();
//...
}
I think this is not the recommended approach since it would incur constructing extra temporaries.
What do you think ? Also, do you recommend a better way to handle this instead ?
First, let’s look into the things that come into play here:
(a) Extending lifetime of a temporary when it’s used to initialize a reference – I learnt about it in this publication by Andrei Anexandrescu. Again, it feels weird but useful:
The rule says that when a reference is initialized with a temporary, the temporary’s lifetime is extended until the reference goes out of scope. (this reply quotes the canon)
(b) Return Value Optimization – (wikipedia) – the two copies local –> return value –> local may be omitted under circumstances. That’s a surprising rule, as it allows the compiler to change the observable behavior, but useful.
There you have it. C++ – weird but useful.
So looking at your scenarios
Scenario A: you are returning a temporary, and bind it to a reference – the temporary’s lifetime is extended to the lifetime of l_Foo.
Note that this wouldn’t work if
GetFoowould return a reference rather than a temporary.Scenario B: Works, except that it forces a Construct-Construct-Copy-Cycle (which may be much more expensive than single construct), and the problem you mention about requiring a default constructor.
I wouldn’t use that pattern to create a object – only to mutate an existing one.
Scenario C: The copies of temporaries can be omitted by the compiler (as of RVO rule). There is unfortunately no guarantee – but modern compilers do implement RVO.
Rvalue references in C++ 0x allows Foo to implement a resource pilfering constructor that not only guarantees supression of the copies, but comes in handy in other scenarios as well.
(I doubt that there’s a compiler that implements rvalue references but not RVO. However there are scenarios where RVO can’t kick in.)
A question like this requires mentioning smart pointers, such as
shared_ptrandunique_ptr(the latter being a “safe”auto_ptr). They are also in C++ 0x. They provide an alternate pattern for functions creating objects.