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?

Carl Norum
  • 219,201
  • 40
  • 422
  • 469
moswald
  • 11,491
  • 7
  • 52
  • 78
  • 1
    There was a good [SO question](http://stackoverflow.com/questions/9322174/move-assignment-operator-and-if-this-rhs) which you might want to look at. – Jesse Good Feb 22 '12 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. – Johannes Schaub - litb Feb 22 '12 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? – moswald Feb 22 '12 at 22:35
  • If by "generally accepted pattern for copy constructor + assignment operator" you mean "copy and swap", see this link: http://stackoverflow.com/a/9322542/576911 – Howard Hinnant Feb 23 '12 at 00:01

4 Answers4

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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 1
    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. :) – moswald Feb 22 '12 at 21:44
  • 1
    Minor refactoring would probably eliminate that issue, though. I'll think about it. I don't know if the end result will be simpler. – moswald Feb 22 '12 at 21:45
  • Unfortunately, not all compilers generate default move constructor/assignment operator. VC2010 does not, not sure about VC2011. – lapk Feb 22 '12 at 22:09
  • @AzzA: 2011 doesn't do it either. – Nicol Bolas Feb 22 '12 at 22:23
  • Even if you're holding external resources you may be able to use the defaults if you hold that resource by, e.g., a shared_ptr. – bames53 Feb 22 '12 at 22:35
  • 1
    pprefer unique_ptr to shared_ptr. Both can clean up arbitrary resources though. – Mooing Duck Feb 22 '12 at 22:49
  • In some cases you could have a member pointing to the object: `oldfoo.bar->setParent(newFoo)` – josefx Feb 24 '12 at 12:35
  • 1
    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. – Dave Abrahams Sep 18 '12 at 15:32
  • @Dave: Care to give such a counter-example? I don't really see what you mean. – Xeo Sep 18 '12 at 15:39
  • 1
    @Xeo: sure. `class X { std::vector 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. – Dave Abrahams Sep 24 '12 at 00:21
  • @Dave: I see. I personally think that moved-from objects shouldn't have any invariants, though, since they're basically dead objects. What good is it, if I return an `X` from a function, move it into some variable, and the moved-from temporary then goes and fills `y` back up only to be destroyed seconds later? If you want that, define move members as `= delete` and only allow copies (which is likely a better thing anyway, just copying and not moving and filling back up (yes, move-only types don't play well with that idea)). – Xeo Sep 24 '12 at 00:34
  • 1
    @Xeo: yes, it is possible to build a logically coherent worldview where classes have “invariants” *and* “special states” outside those “invariants,” but is _much_ harder to think about correctness in that world than in a world where invariants truly *are* invariant (except during mutation, yadayada). I have yet to run into a real-world case that needed that more complicated approach. – Dave Abrahams Sep 24 '12 at 00:57
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.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 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](http://ideone.com/WQVXK) uses OP's version `T & operator( T )`, [second](http://ideone.com/SXgVN) uses explicitly defined `T &( T && )` and `T & operator=( T && )`. Compare the output between `-----------`... – lapk Feb 22 '12 at 22:46
  • 1
    @AzzA: I'm just comparing the OP's solution to one that I think is better. Nothing more, nothing less. – Howard Hinnant Feb 23 '12 at 03:54
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;
};
moswald
  • 11,491
  • 7
  • 52
  • 78
  • Why would you make swap private? – ronag Feb 22 '12 at 21:37
  • @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). – moswald Feb 22 '12 at 21:48
  • 4
    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. – Howard Hinnant Feb 22 '12 at 21:51
  • @mos Did you actually consider what's happening when you do something like `MyClass lC = MyClass(), lC1; lC1 = lC;`? – lapk Feb 22 '12 at 21:55
  • @ronag: Touche :) There's no true reason to keep it private. – moswald Feb 22 '12 at 22:36
  • @AzzA: his assignment operator takes by value, so theres no issue. – Mooing Duck Feb 22 '12 at 22:51
  • @MooingDuck Please, see my comment to Howard's answer. Maybe, I'm missing something, but I find an issue with extra calls. – lapk Feb 22 '12 at 22:57