22

I have a C++ program that uses a std::list containing instances of a class. If I call e.g. myList.push_back(MyClass(variable)); it goes through the process of creating a temporary variable, and then immediately copies it to the vector, and afterwards deletes the temporary variable. This is not nearly as efficient as I want, and sucks when you need a deep copy.

I would love to have the constructor of my class new something and not have to implement a copy constructor just to allocate my memory for the second time and waste runtime. I'd also rather not have to immediately find the class instance from the vector/list and then manually allocate the memory (or do something horrible like allocate the memory in the copy constructor itself).

Is there any way around this (I'm not using Visual Studio BTW)?

Michael Myers
  • 188,989
  • 46
  • 291
  • 292
Warpspace
  • 3,215
  • 3
  • 21
  • 24
  • 1
    What compiler are you using? I believe this case is usually optimized in newer compilers. – Michael Myers Apr 05 '10 at 18:27
  • Are you performing this analysis with an optimized build? – Michael Burr Apr 05 '10 at 18:33
  • You need to test with a release build. It may be doing this in DEBUG mode, but use RVO (return-value optimization) in release mode, which eliminates the copy. – Nate Apr 05 '10 at 18:42
  • This isn't the same thing as RVO; I just tested on MSVC2008, and while a release build does perform RVO, it still copies in this case, even with all optimization enabled. I've generally just used a pointer container when I needed to do this. – tzaman Apr 05 '10 at 18:56
  • Slightly off-topic: Visual C++ performs RVO even on debug builds, but this is not really a RVO scenario. – Nemanja Trifunovic Apr 05 '10 at 19:17
  • I stand corrected then. Thanks. – Nate Apr 05 '10 at 20:24
  • I'm using G++ (under Linux, but sometimes using MinGW under Windows). I did a test on a Debug build (no optimisation flags, but set the C++0x flag), and it was indeed fully-constructing a temporary and deleting it straight after copying it to the vector/list. – Warpspace Apr 06 '10 at 05:51

7 Answers7

9

Ahem. In the interests of science, I've whipped up a tiny test program to check whether the compiler elides the copy or not:

#include <iostream>
#include <list>
using namespace std;

class Test
{
public:
  Test() { cout<<"Construct\n"; }
  Test(const Test& other) { cout<<"Copy\n"; }
  Test& operator=(const Test& other) { cout<<"Assign\n"; return (*this); }
};

Test rvo() { return Test(); }
int main()
{
  cout<<"Testing rvo:\n";
  Test t = rvo();
  cout<<"Testing list insert:\n";
  list<Test> l;
  l.push_back(Test());
}

And here's my output on MSVC++2008:

Testing rvo:
Construct 
Testing list insert:
Construct
Copy

It's the same for both debug and release builds: RVO works, temporary object passing isn't optimized.
If I'm not mistaken, the Rvalue references being added in the C++0x standard are intended to solve this very problem.

tzaman
  • 46,925
  • 11
  • 90
  • 115
9

C++0x move constructors are a partial workaround: instead of the copy constructor being invoked, the move constructor would be. The move constructor is like the copy constructor except it's allowed to invalidate the source argument.

C++0x adds another feature which would do exactly what you want: emplace_back. (N3092 §23.2.3) You pass it the arguments to the constructor, then it calls the constructor with those arguments (by ... and forwarding) so no other constructor can ever be invoked.

As for C++03, your only option is to add an uninitialized state to your class. Perform actual construction in another function called immediately after push_back. boost::optional might help you avoid initializing members of the class, but it in turn requires they be copy-constructible. Or, as Fred says, accomplish the same thing with initially-empty smart pointers.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • That's more what I'm looking for. I have a C++0x enabled compiler, and like the convenience of std::lists without the memory/heap allocation drawback of a large number of pointers. Funny I never heard of emplace_back, which is what I get for just looking at the wikipedia entry for C++0x. Where can I find more information about it? – Warpspace Apr 06 '10 at 06:03
  • @Warpspace: Download N3092 (google for "C++ N3092") and see Stroustrup's C++0x FAQ http://www2.research.att.com/~bs/C++0xFAQ.html . I noticed that Microsoft has incorrect documentation for `emplace`, claiming it uses move semantics. (The same thing is accomplished by `push_back(move(…))`.) So if you're on MSVC you might still be out of luck for the moment. – Potatoswatter Apr 06 '10 at 07:25
5

C++ 0x move constructors (available with VC++ 2010 and recent GNU compilers) are exactly what you are looking for.

Nemanja Trifunovic
  • 24,346
  • 3
  • 50
  • 88
  • Move constructors require an uninitialized state for the object. If he has an uninitialized state, he can default-construct to that with `push_back(MyClass())` and then `initialize` his object in place. So I don't think `move` solves anything here. – Potatoswatter Apr 05 '10 at 23:17
  • I'm not sure if it's my compiler or not, but with C++0x (under G++) enabled, the move constructor appears to not be called, as my object gets deleted twice (implying the copy constructor is used). What Potatocorn mentions is my current implementation, which I'm trying to avoid due to severe code duplication (or the need for a dedicated function). – Warpspace Apr 06 '10 at 06:24
4

In fact, the compiler might elide the copy in this case.

If your compiler doesn't do this, one way to avoid copying would be to have your list contain pointers instead of instances. You could use smart pointers to clean up the objects for you.

Fred Larson
  • 60,987
  • 18
  • 112
  • 174
  • 2
    The compiler can't elide the copy. The object is constructed on the stack and passed by const reference to `vector<>::allocator_type::construct` to move it to the heap. It's constructed before the call to `push_back` begins and the destination location isn't known until `allocator_type::allocate` inside `push_back`. The forces of time and space do not cooperate. – Potatoswatter Apr 05 '10 at 23:04
2

Check out Boost's ptr_container library. I use the ptr_vector in particular:

boost::ptr_vector<Foo> c;
c.push_back(new Foo(1,2,3) );
c[0].doSomething()

and when it goes out of scope, delete will be called on each element of the vector.

Seth Johnson
  • 14,762
  • 6
  • 59
  • 85
1

Use a shared_ptr or shared_array to manage the memory your class wants to allocate. Then the compiler-provided copy-constructor will simply increment a reference count as the shared_ptr copies itself. It's an important usage concept for standard containers that your elements be cheap to copy. The standard library makes copies all over the place.

Michael Kristofik
  • 34,290
  • 15
  • 75
  • 125
0

I would suggest using an std::vector<std::unique_ptr>, because it automatically releases the memory when needed, with less overhead than std::shared_ptr. The only caveat is that this pointer doesn't have reference counting, and so you have to be careful not to copy the pointer itself somewhere else, lest the data be deleted when it is still used somewhere else.

Morgan H
  • 70
  • 1
  • 10