When to use Move Constructors/Assignments
Asked Answered
J

3

10

I've searched but cannot find the answer to "When" to use them. I just keep hearing that it's good because it saves me that extra copy. I went around putting it in every class I had but some how that didn't seem to make sense for some classes :S I've read countless tutorials on LValues and RValues and std::move vs. std::copy vs. memcpy vs. memmove, etc. And even read up on throw() but I'm not sure on when to use that either.

My code looks like:

struct Point
{
    int X, Y;

    Point();
    Point(int x, int y);
    ~Point();

    //All my other operators here..
};

Then I have a class array of that like(RAII sorta thing):

class PA
{
    private:
        std::vector<Point> PointsList;

    public:
        PA();
        //Variadic Template constructor here..
        ~PA();
        //Operators here..
 };

Should I be using a move constructor and copy constructor? I had it in the Point Class but it felt weird so I removed it. Then I had it in the PA class but I thought that it won't do anything much so I removed it too. Then in my bitmaps class my compiler was complaining about having pointer members but no overload so I did:

//Copy Con:
BMPS::BMPS(const BMPS& Bmp) : Bytes(((Bmp.width * Bmp.height) != 0) ? new RGB[Bmp.width * Bmp.height] : nullptr), width(Bmp.width), height(Bmp.height), size(Bmp.size), DC(0), Image(0)
{
    std::copy(Bmp.Bytes, Bmp.Bytes + (width * height), Bytes);
    BMInfo = Bmp.BMInfo;
    bFHeader = Bmp.bFHeader;
}

//Move Con:
BMPS::BMPS(BMPS&& Bmp) : Bytes(nullptr), width(Bmp.width), height(Bmp.height), size(Bmp.size), DC(0), Image(0)
{
    Bmp.Swap(*this);
    Bmp.Bytes = nullptr;
}

//Assignment:
BMPS& BMPS::operator = (BMPS Bmp)
{
    Bmp.Swap(*this);
    return *this;
}

//Not sure if I need Copy Assignment?

//Move Assignment:
BMPS& BMPS::operator = (BMPS&& Bmp)
{
    this->Swap(Bmp);
    return *this;
}

//Swap function (Member vs. Non-member?)
void BMPS::Swap(BMPS& Bmp) //throw()
{
    //I was told I should put using std::swap instead here.. for some ADL thing.
    //But I always learned that using is bad in headers.
    std::swap(Bytes, Bmp.Bytes);
    std::swap(BMInfo, Bmp.BMInfo);
    std::swap(width, Bmp.width);
    std::swap(height, Bmp.height);
    std::swap(size, Bmp.size);
    std::swap(bFHeader, Bmp.bFHeader);
}

Is this correct? Did I do something bad or wrong? Do I need throw()? Should my assignment and move assignment operators actually be the same like that? Do I need a copy assignment? Ahh so many questions :c The last forum I asked on could not answer all so I was left confused. Finally should I use unique_ptr for Bytes? (Which is an array of bytes/pixels.)

Jurel answered 18/6, 2012 at 4:56 Comment(0)
L
17

There are some excellent thought's on Scott Meyer's blog:

First, not all copy requests can be replaced by moves. Only copy requests for rvalues are eligible for the optimization. Second, not all types support move operations that are more efficient than copying operations. An example is a std::array. Third, even types that support efficient move operations may support them only some of the time. Case in point: std::string. It supports moves, but in cases where std::string is implemented using SSO (the small string optimization), small strings are just as expensive to move as to copy!

Perhaps, you can categorize your types accordingly and then decide which all need move semantics. Note, that there are restrictions on the compiler auto-generating move ctors/assignment operators, so you would be well advised to keep those in mind. This helps when you are explicitly specifying move members.

For classes where you do not specify move members explicitly, there are a few spots of bother. There is also the problem of explicitly/implicitly deleted move member which inhibit copying from rvalues. A highly valuable source of issues with implicit generation of move members can be found in Stroustrup's paper titled To Move or Not to Move.

Regarding exception handling with move semantics I'd suggest Dave Abraham's post Exceptionally Moving.

I'll try to come back to this answer with some examples when I get the time. Hopefully, for the time being the above mentioned links will help you get started.

Libertylibia answered 18/6, 2012 at 5:27 Comment(1)
This definitely got me started and thinking. It didn't answer some of the questions I would have liked to know specific answers for but it is more than good enough for me to start learning for myself. Thank you very much.Jurel
D
5

First and foremost: Use the "Rule of Zero" wherever you can. See "The rule of three/five/zero" and "C-20".

Therefore: Your "weird" feeling was correct: Point and PA do not need explicit copy/move operators. Otherwise, the answers of dirkgently and dirvine and their references are fine reads for a more in-depths understanding.

As for BMPS it is certainly a good idea to provide explicit move operators.

Detective answered 19/10, 2018 at 8:7 Comment(1)
Thanks for your terse answer, very helpful. The easy-to-remember lesson from the first weblink says it all: "Classes that have custom destructors, copy/move constructors or copy/move assignment operators should deal exclusively with ownership"Cheeks
C
3

Whilst move is a very good tool in the bag that it allows much more than speed. With move you are moving the object (obviously) and leaving nothing behind (apart from an empty carcass, or more accurately a default constructed object). This then forces you to think more carefully about ownership and design of the program when move objects are favoured. Instead of multiple access to some objects or sharing, move clearly forces you to consider who has the object and when.

As Bjarne Stroustrup has stated previously, we should stop sharing everything and having pointers everywhere. If using pointers use unique_ptr and not shared_ptr unless you absolutely want to share ownership (which in many cases you don't). Unique_ptr and it's move only (well deleted copy anyway) constructor is a good example of an object that should provide move and never copy.

Move is great and writing move constructors is a very good idea, even better when msvc catches up and allows the deleted/default decorators on the otherwise compiler generated (copy/assign etc.) constructors. Errors like an attempt to access a previously deleted member are very helpful here, simply making some constructors private has a less obvious intent to a code maintainer. In cases where copy is OK but move preferred a compiler will hopefully chose to move when it can (i.e. test with vector.push_back which will with some compilers move or emplace_back if it's reasonable, buying instant performance gains), therefore even in copy constructor objects a defined move constructor may automatically be selected improving performance (well ignoring all the SSO discussions that are raging at the moment). This is a decent answer to peruse

There is a few pretty heavy threads on the boost mailing list about advantage/disadvantage of move/copy and pass by value/reference which are all very much talking about similar issues, if you are looking for further info.

Chyle answered 18/6, 2012 at 17:24 Comment(1)
I definitely appreciated that link and the explanation. Thank you too! Link was most definitely helpful.Jurel

© 2022 - 2024 — McMap. All rights reserved.