tags:

views:

226

answers:

6

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

+5  A: 

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
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
+2  A: 

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
...without the overhead of `shared_ptr` -- +1
vladr
+1  A: 

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.

Kristo
+5  A: 

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

Nemanja Trifunovic
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
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
+4  A: 

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
+1 for **science**!
Wesley Petrowski
+2  A: 

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
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 <code>emplace_back</code>, which is what I get for just looking at the wikipedia entry for C++0x. Where can I find more information about it?
Warpspace
@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