I have been experimenting with C++11 again recently, after some absence, and after reading many articles on the internet I am now thoroughly confused about what is the most efficient way to return large objects from factory functions (basically, data analysis from a database).
I have become a fan of unique_ptr, but I read in several articles that because of the new move-constructors it is now perfectly possible to return a big vector say by value and because of these new semantics it should be as fast as copying one pointer.
To try this out, I wrote a small test program with outputs in the various constructors:
#include <iostream>
#include <memory>
using namespace std;
class C {
public:
C( string n ) : _name{n} { cout << "Constructing a C named '" << _name << "'\n"; };
C() : _name( "EMPTY" ) { cout << "Default-constructing a C named '" << _name << "'\n"; } ; // default-ctor
C( const C& c ) : _name{c._name} {
_name += " [copied]";
cout << "Copy-constructing a C named '" << _name << "'\n";
};
C( C&& c )
: _name{c._name} {
_name += " [moved]";
cout << "Move-constructing a C named '" << _name << "'\n";
};
~C() { cout << "Destructing a C named '" << _name << "'\n"; };
string getName() { return _name; };
private:
string _name;
};
and tested with
C fooVal() {
cout << "In fooVal\n";
string str = "value return";
C c(str);
return c;
}
C& fooRef() {
cout << "In fooRef\n";
string str = "reference return";
C* pC = new C( str );
return *pC;
}
C* fooPtr() {
cout << "In fooPtr\n";
string str = "classical pointer return";
C* pC = new C( str );
return pC;
}
unique_ptr<C> fooUPtr() {
cout << "In fooUPtr\n";
string str = "unique_ptr return";
return unique_ptr<C>(new C(str));
}
shared_ptr<C> fooSPtr() {
cout << "In fooSPtr\n";
string str = "shared_ptr return";
return shared_ptr<C>(new C(str));
}
// IMPORTANT: THIS NEEDS TO BE COMPILED WITH FLAG -fno-elide-constructors
int main(int argc, const char * argv[])
{
C cv(fooVal());
cout << "cv constructed\n";
C& cr = fooRef();
cout << "cr constructed\n";
C* pC = fooPtr();
cout << "*pC constructed\n";
unique_ptr<C> upC = fooUPtr();
cout << "*upC constructed\n";
shared_ptr<C> spC = fooSPtr();
cout << "*spC constructed\n";
cout << "Alive: " << cv.getName() << ", " << cr.getName() << ", " << pC->getName() << ", " << upC->getName() << ".\n";
}
Now, if I just compile this as-is, the compiler optimizes away ("elides") various constructor calls and I get the output:
In fooVal
Constructing a C named 'value return'
cv constructed
In fooRef
Constructing a C named 'reference return'
cr constructed
In fooPtr
Constructing a C named 'classical pointer return'
*pC constructed
In fooUPtr
Constructing a C named 'unique_ptr return'
*upC constructed
In fooSPtr
Constructing a C named 'shared_ptr return'
*spC constructed
Alive: value return, reference return, classical pointer return, unique_ptr return.
Destructing a C named 'shared_ptr return'
Destructing a C named 'unique_ptr return'
Destructing a C named 'value return'
OK, but you can see how much copying has been optimized away. To see what is the "specified" behaviour, I compiled this with the flag -fno-elide-constructors (I'm using Apple LLVM version 4.2 (clang-425.0.28)). But then I get the following output:
In fooVal
Constructing a C named 'value return'
Destructing a C named 'value return'
Move-constructing a C named ' [moved]'
Destructing a C named ''
cv constructed
In fooRef
Constructing a C named 'reference return'
cr constructed
In fooPtr
Constructing a C named 'classical pointer return'
*pC constructed
In fooUPtr
Constructing a C named 'unique_ptr return'
*upC constructed
In fooSPtr
Constructing a C named 'shared_ptr return'
*spC constructed
Alive: [moved], reference return, classical pointer return, unique_ptr return.
Destructing a C named 'shared_ptr return'
Destructing a C named 'unique_ptr return'
Destructing a C named ' [moved]'
So, clearly, something fishy is going on with the value-returned object. Obviously, this is more than just a small problem, because I would have expected -fno-elide-constructors not to change semantics, only the amount of constructors involved.
Thus I ask:
- What is going on? Why does the value object "lose" its string parameter? And where?
- It looks like value returns have problems whereas the other ones work just fine. So why are people recommending nowadays that we return by value and "the system takes care of the rest"?
- What is a good way to return large objects?
- Am I making a mistake somewhere that I am not seeing?
Thanks!