Strange behavior of copy / move-constructors and how to return large objects?

I recently experimented again with C ++ 11, after some absence, and after reading many articles on the Internet, I am now completely confused by what is the most efficient way to return large objects from factory functions (mainly analyzing data from a database).

I became a fan of unique_ptr, but I read in several articles that because of the new constructors move it is now quite possible to return a large vector by value, and because of this new semantics it should be as fast as copying a single pointer.

To try this, I wrote a small test program with outputs in 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 using

 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 it as-is, the compiler optimizes ("elides") the 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 many copies have been optimized. To find out what the β€œspecified” behavior is, I compiled it using the -fno-elide-constructors flags (I use 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, it is clear that something suspicious is happening with the return value of the object. Obviously, this is more than just a small problem, because I expected -fno-elide-constructors to not change the semantics, but only the number of involved constructors.

So I ask:

  • What's happening? Why does the value object "lose" its string parameter? And where?
  • Returning the value seems to have problems, while others are working fine. So why do people recommend these days that we are returning in 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 don't see?

Thanks!

+4
source share
3 answers

This seems to be a clang error: http://llvm.org/bugs/show_bug.cgi?id=12208 , which, although simplified, is related to string concatenation and apparently has not yet been fixed.

+3
source

Honestly, I do not think -fno-elide-constructors leads to a valid program.

As soon as it crashes to my system, and valgrind quickly pointed out the main errors:

 ==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) 

It is used

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

It may be a compiler error, or it may be a Read Documentation case regarding -fno-elide-constructors . I did not check.

+1
source

This is how I wrote fooVal :

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

.. And this is how I write C::C(C&&) - although it should be generated automatically in this case ...

 C( C&& c ) : _name{std::move(c._name)} // note the "std::move" { _name += " [moved]"; cout << "Move-constructing a C named '" << _name << "'\n"; }; 
-one
source

Source: https://habr.com/ru/post/1497849/


All Articles