4

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:

  1. What is going on? Why does the value object "lose" its string parameter? And where?
  2. 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"?
  3. What is a good way to return large objects?
  4. Am I making a mistake somewhere that I am not seeing?

Thanks!

4

3 回答 3

3

看起来这是这个clang错误:http ://llvm.org/bugs/show_bug.cgi?id=12208虽然简化但与字符串连接相关,但显然仍未修复。

于 2013-08-19T18:06:09.283 回答
1

老实说,我认为 -fno-elide-constructors 不会产生有效的程序。

因为它会立即在我的系统上崩溃,并且 valgrind 很快指出了主要错误:

==6098== Memcheck, a memory error detector
==6098== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==6098== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==6098== Command: ./test
==6098== 
In fooVal
Constructing a C named 'value return'
Destructing a C named 'value return'
==6098== Use of uninitialised value of size 8
==6098==    at 0x4EEF83B: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17)
==6098==    by 0x40297C: C::C(C&&) (test.cpp:19)
==6098==    by 0x401C0C: C::C(C&&) (test.cpp:22)
==6098==    by 0x40165D: main (test.cpp:69)
==6098== 
==6098== Conditional jump or move depends on uninitialised value(s)
==6098==    at 0x4EEF84D: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17)
==6098==    by 0x40297C: C::C(C&&) (test.cpp:19)
==6098==    by 0x401C0C: C::C(C&&) (test.cpp:22)
==6098==    by 0x40165D: main (test.cpp:69)

这是使用

Ubuntu clang version 3.2-9 (tags/RELEASE_32/final) (based on LLVM 3.2)
Target: x86_64-pc-linux-gnu
Thread model: posix

这可能是编译器错误,或者可能是关于-fno-elide-constructors. 我没有检查。

于 2013-08-19T17:05:41.897 回答
-1

我就是这样写的fooVal

C fooVal() 
{    
    cout << "In fooVal\n";
    return C("value return");
}

..这就是我的写作方式C::C(C&&)-尽管在这种情况下应该自动生成...

C( C&& c ) : _name{std::move(c._name)} //  note the "std::move"
{
    _name += " [moved]";
    cout << "Move-constructing a C named '" << _name << "'\n";
};
于 2013-08-19T18:13:21.483 回答