10

At this point, writing the copy constructor and assignment operator pair is well-defined; a quick search will lead you to plenty of hits on how to properly code these.

Now that the move constructor has entered the mix, is there a new "best" way?

4
  • 1
    There was a good SO question which you might want to look at. Commented Feb 22, 2012 at 21:38
  • 1
    This question is too broad. You need to tear it down to a specific scenario. As there is no recipe of how to write the copy constructor and assignment operator in "well-defined" manner for each and every class. Same for your question. Commented Feb 22, 2012 at 21:52
  • Why is it too broad? There's a generally accepted pattern for copy constructor + assignment operator, why not for move constructor as well? Commented Feb 22, 2012 at 22:35
  • If by "generally accepted pattern for copy constructor + assignment operator" you mean "copy and swap", see this link: stackoverflow.com/a/9322542/576911 Commented Feb 23, 2012 at 0:01

4 Answers 4

13

Preferably, they'll just be = default;, since the member types should be of resource managing types that hide the move details from you, like std::unique_ptr. Only the implementors of those "low level" types should bother with dealing with that.

Remember that you only need to bother with move semantics if you're holding an external (to your object) resource. It's completely useless for "flat" types.

Sign up to request clarification or add additional context in comments.

12 Comments

That's a valid point. I suppose I could have qualified with "if you were going to write your own", but your answer is very correct for people who don't realize they don't need to do this. However, in my case, I'm holding an external resource, which is what spawned this question. :)
Minor refactoring would probably eliminate that issue, though. I'll think about it. I don't know if the end result will be simpler.
pprefer unique_ptr to shared_ptr. Both can clean up arbitrary resources though.
Actually, = default is only going to work for you if the combined moved-from state of all sub-objects meets the invariant of the containing type, and that has no inherent relationship to whether the sub-objects are "resource-managing types." Usually, that will be the case when the containing type is default-constructible, but even then maybe not. It's easy to come up with counter-examples.
@Xeo: sure. class X { std::vector<int> y = {1,2,3}; }; where the desired invariant is that y always has three elements. You can default-construct X, but its =default move constructor will not uphold the desired invariant.
|
6

The best way is to let the compiler generate them all. It was also the best approach in C++03 and if you managed to do this your C++03 classes automatically become "move-enabled" when you migrate to C++11.

Most resource management issues can be solved by writing just the non-copy constructors and destructors of single-resource managing classes and then only making composite classes using these, plus smart pointers (e.g std::unique_ptr) and container classes to build richer objects.

Comments

4

Using clang/libc++:

#include <chrono> #include <iostream> #include <vector> #if SLOW_DOWN class MyClass { void Swap(MyClass &other) { std::swap(other.member, member); } public: MyClass() : member() { } MyClass(const MyClass &other) : member(other.member) { } MyClass(MyClass &&other) : member(std::move(other.member)) { } MyClass &operator=(MyClass other) { other.Swap(*this); return *this; } private: int member; }; #else class MyClass { public: MyClass() : member() { } private: int member; }; #endif int main() { typedef std::chrono::high_resolution_clock Clock; typedef std::chrono::duration<float, std::milli> ms; auto t0 = Clock::now(); for (int k = 0; k < 100; ++k) { std::vector<MyClass> v; for (int i = 0; i < 1000000; ++i) v.push_back(MyClass()); } auto t1 = Clock::now(); std::cout << ms(t1-t0).count() << " ms\n"; } $ clang++ -stdlib=libc++ -std=c++11 -O3 -DSLOW_DOWN test.cpp $ a.out 519.736 ms $ a.out 517.036 ms $ a.out 524.443 ms $ clang++ -stdlib=libc++ -std=c++11 -O3 test.cpp $ a.out 463.968 ms $ a.out 458.702 ms $ a.out 464.441 ms 

This looks like approximately 12% speed difference on this test.

Explanation: One of these definitions has a trivial copy constructor and copy assignment operator. The other doesn't. "Trivial" has a real meaning in C++11. It means the implementation is allowed to use memcpy to copy your class. Or to even copy large arrays of your class. So it is best to make your special members trivial if you can. That means letting the compiler define them. Though you can still declare them with = default if you prefer.

2 Comments

Howard, SLOW_DOWN is just a very inefficient version. It's not even about comparison to compiler generated constructors etc... Here is two codes on ideone.com: first one uses OP's version T & operator( T ), second uses explicitly defined T &( T && ) and T & operator=( T && ). Compare the output between -----------...
@AzzA: I'm just comparing the OP's solution to one that I think is better. Nothing more, nothing less.
2

This is what I've come up with, but I don't know if there's a more optimal solution out there.

class MyClass { void Swap(MyClass &other) { std::swap(other.member, member); } public: MyClass() : member() { } MyClass(const MyClass &other) : member(other.member) { } MyClass(MyClass &&other) : member(std::move(other.member)) { } MyClass &operator=(MyClass other) { other.Swap(*this); return *this; } private: int member; }; 

7 Comments

Why would you make swap private?
@ronag: in my real-world class, there's no real need for it (yet). I'd rather not expose an API that someone might come along later and use, possibly incorrectly (although I don't know how they could abuse something as simple as swap).
If MyClass really is supposed to have these semantics, this is probably the very worst (poorest performing) way possible to write the special members and yet still have it be considered correct. Sorry, to be so direct, but I thought you should know.
@mos Did you actually consider what's happening when you do something like MyClass lC = MyClass(), lC1; lC1 = lC;?
@ronag: Touche :) There's no true reason to keep it private.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.