5

Are the following assignment and copy move constructors the most efficient? if anybody have other way please tell me? I mean what bout std::swap? and calling assignment through copy constructor is safe in the code below?

#include <iostream>
#include <functional>
#include <algorithm>
#include <utility>

using std::cout;
using std::cin;
using std::endl;
using std::bind;


class Widget
{

public:

    Widget(int length)
        :length_(length),
        data_(new int[length])
    {
        cout<<__FUNCTION__<<"("<<length<<")"<<endl;
    }

    ~Widget()
    {
        cout<<endl<<__FUNCTION__<<"()"<<endl;
        if (data_)
        {
            cout<<"deleting source"<<endl;
        } 
        else
        {
            cout<<"deleting Moved object"<<endl;
        }

        cout<<endl<<endl;
    }

    Widget(const Widget& other)
        :length_(other.length_),
        data_(new int[length_])
    {
        cout<<__FUNCTION__<<"(const Widget& other)"<<endl;
        std::copy(other.data_,other.data_ + length_,data_);
    }

    Widget(Widget&& other)
/*
        :length_(other.length_),
        data_(new int[length_])*/
    {
        cout<<__FUNCTION__<<"(Widget&& other)"<<endl;
        length_ = 0;
        data_ = nullptr;
        std::swap(length_,other.length_);
        std::swap(data_,other.data_);
    }

    Widget& operator = (Widget&& other)
    {
        cout<<__FUNCTION__<<"(Widget&& other)"<<endl;

        std::swap(length_,other.length_);
        std::swap(data_,other.data_);

        return *this;
    }

    Widget& operator = (const Widget& other)
    {
        cout<<__FUNCTION__<<"(const Widget& other)"<<endl;
        Widget tem(other);
        std::swap(length_,tem.length_);
        std::swap(data_,tem.data_);

        return *this;
    }
    int length()
    {
        return length_;
    }

private:

    int length_;
    int* data_;
};



int main()
{
    {
        Widget w1(1);
        Widget w2(std::move(Widget(2)));

        w1 = std::move(w2);
    }


    cout<<"ENTER"<<endl;
    cin.get();
    return 0;
}
ildjarn
  • 62,044
  • 9
  • 127
  • 211
Abdulrhman
  • 109
  • 2
  • 10
  • 3
    If you used a `std::vector` as a member of the class, you wouldn't need to write a copy or move constructor/assignment (unless you're using VS, where you'd need the moves but not the copies). – Nicol Bolas Sep 29 '12 at 14:27

3 Answers3

7

Looks fine from an efficiency POV, but contains an awful lot of duplicated code. I'd

  • Implement a swap() operator for your class.
  • Initialize length_ and data_ where they are declared.
  • Implement operations in terms of other operations whereever possible.

You might want to use std::memcpy instead of std::copy since you're dealing with a raw array anyway. Some compilers will do that for you, but probably not all of them...

Here's a de-duplicated version of your code. Note how there is only one place which needs to know how two instances of Widget are swapped. And only one place which knows how to allocate a Widget of a given size.

Edit: You usually also want to use argument-dependent lookup to locate swap, just in case you ever have non-primitive members.

Edit: Integrated @Philipp's suggestion of making the assignment operator take it's argument by value. That way, it acts as both move assignment and copy assignment operator. In the move case, not that if you pass a temporary, it won't be copied, since the move constructor, not the copy constructor will be used to pass the argument.

Edit: C++11 allows non-cost members to be called on rvalues for compatibility with previous versions of the standard. This allows weird code like Widget(...) = someWidget to compile. Making operator= require an lvalue for this by putting & after the declaration prevents that. Note though that the code is correct even without that restriction, but it nevertheless seems like a good idea, so I added it.

Edit: As Guillaume Papin pointed out, the destructor should use delete[] instead of plain delete. The C++ standard mandates that memory allocated via new [] be deleted via delete [], i.e. it allows new' andnew []` to use different heaps.

class Widget
{
public:
    Widget(int length)
        :length_(length)
        ,data_(new int[length])
    {}

    ~Widget()
    {
        delete[] data_;
    }

    Widget(const Widget& other)
        :Widget(other.length_)
    {
        std::copy(other.data_, other.data_ + length_, data_);
    }

    Widget(Widget&& other)
    {
        swap(*this, other);
    }

    Widget& operator= (Widget other) &
    {
        swap(*this, other);
        return *this;
    }

    int length() const
    {
        return length_;
    }

private:
    friend void swap(Widget& a, Widget& b);

    int length_ = 0;
    int* data_ = nullptr;
};

void swap(Widget& a, Widget& b) {
    using std::swap;
    swap(a.length_, b.length_);
    swap(a.data_, b.data_);
}
Community
  • 1
  • 1
fgp
  • 8,126
  • 1
  • 17
  • 18
  • thank you, I think it is the most efficient code now, but what if class client wrote someWidget = std::move(anyWidget) = std::move(otherWidget) I think making other pointers as nullptrs after swap is the correct answer – Abdulrhman Sep 30 '12 at 09:11
  • Only if you `delete` them beforehand, otherwise you'll leaking memory, no? What's the problem with that double-move anyway? AFAICS, your example is equivalent to `swap(otherWidget,anyWidget);someWidget=anyWidget` and hence to `someWidget=otherWidget;swap(otherWidget,anyWidget)`. – fgp Sep 30 '12 at 09:37
  • the pointers of the moved objects will point to another place and they might be used before destruction – Abdulrhman Sep 30 '12 at 10:13
  • the return value of operator= is lvalue so we must make the pointers null after moving it – Abdulrhman Sep 30 '12 at 10:20
  • instead of writing two assignment operators, you could write just one that takes its argument by value. That one can be used for both copy and move assignment. – Philipp Sep 30 '12 at 10:54
  • @Philipp. Won't that cause an unnecessary copy if you assign from a temporary, i.e. if you do `a = Widget(b)`? – fgp Sep 30 '12 at 22:30
  • @Philipp, again: 'course it won't, my brains seems to have momentarily snapped out of c++11 mode :-(. It will, of course, use the move constructor to pass the argument if the source is a temporary, not the copy constructor. So, yeah, that's a neat trick. – fgp Sep 30 '12 at 22:34
  • @Abdulrhman I still don't get it. The moved objects will contain the data of the objects they were moved to after the move. That's fine, though - the data will be free'd once the moved objects are deleted. Since they'll usually be temporaries (otherwise, why move from them?), the delete will happen quickly (i.e., once the statement ends). But even if not, there's still no leak. Now, if Widget allocates *lots* of memory, or holds scarce resources, then you might want to reset moved objects immediatly, and not wait for destruction. But it's not necessary for correctness, AFAICS. – fgp Sep 30 '12 at 22:38
  • @fgp I agree with you, there is no sense of assigning lvalues by making them rvalues using std::move but std::move itself doesn't make the pointers null after deleting them ,we have to do that by ourselves , so I suggest that move assignment should be like this `const Widget& operator= (Widget&& other)` to prevent misuse – Abdulrhman Oct 01 '12 at 06:34
  • @Abdulrhman you're missing the point, I think. Moving assignment (or move construction) doesn't delete *anything*. It simply swaps the contents of the moved object with the target object. Both objects are valid afterwards. The `delete` happens later, when the moved object is destructed. Look at it this way: Only the constructor allocates, only the destructor frees, `data_`-pointers are never duplicated, only swapped. Thus, pointers can never be dangling, and memory can never be leaked. If you're still having doubts, please explain what *exactly* you think could go wrong. – fgp Oct 01 '12 at 08:49
  • @fgp `widget1 = std::move(w2)= std::move(w3)` then `somefunction(w3)` this misuse is not reasonable but it would be compiled, so we have to prevent that by either 'const Widget& operator= (Widget&& other)' or `Widget operator= (Widget&& other)` – Abdulrhman Oct 01 '12 at 09:09
  • I Tested this code `Widget w(2); w = Widget(1) = std::move(w); ` this will stop debugging operation I tested that on MS and GCC compilers – Abdulrhman Oct 01 '12 at 09:19
  • @Abdulrhman I've tested with clang and `-faddress-sanitzer`, and can find no problem. See the new answer I posted. – fgp Oct 01 '12 at 10:42
  • This current code looks very bad to me. The move constructor swaps this->data_ without initialising it first, and so is undefined behaviour. The uninitialised value is left in other.data_, so will likely crash when other is destructed. (It's also inefficient to use swap, which will do three assignments, when only two are needed.) Am I missing something? – Brangdon Oct 01 '12 at 13:34
  • @Brangdon `data_` and `length_` are initialized where they are declared. c++11 allows that, and uses the initializer from the declaration unless the constructor provides an explicit initializer (or calls another constructor). Regarding swap's efficiency - the compiler surely optimizes that, especially since the types involved are all primitive types. – fgp Oct 01 '12 at 17:40
  • "*Default-initialize `length_` and `data_` where they are declared.*" I think you mean _value-initialize_. :-] – ildjarn Oct 02 '12 at 01:09
  • @ildjarn Hm, I didn't mean *default-initialize*, that's for sure ;-) Whether I meant just *initialize* or *value-initialize*, I'm not sure. I *think* value-initialization refers only `T a()` (if T has *no* user-defined constructor), not to `T a = ...`. Anway, will change to say just * initialize*. – fgp Oct 02 '12 at 10:13
  • 1
    The destructor seems wrong, shouldn't `new T[]` have a matching `delete []` (as opposed to `delete` alone)? – Guillaume Papin Apr 04 '13 at 12:20
  • @GuillaumePapin Very true! Fixed. – fgp Apr 04 '13 at 17:45
  • what is the & at end of "Widget& operator= (Widget other) &" also shouldnt the move constructor be noexcept so that it is always picked up ? i would also add #include in the beginning :) – Cpp crusaders Jan 23 '21 at 12:11
  • shouldnt the swap function be public ? – Cpp crusaders Jan 23 '21 at 12:17
0

The answer is in response to @Abdulrhman's complaint in the comments above that things fail for some (obscure) sequences of assignments. Put into a seperate answer because it's more readable that way.

The complaint was that

Widget w(2);
w = Widget(1) = std::move(w);

crashes. Here's the output I get from

Widget w(2);
w.data()[0] = 0xDEAD; w.data()[1] = 0xBEEF;
w = Widget(1) = std::move(w);
std::cerr << std::hex << w.data()[0] << w.data()[1] << std::endl;

with some code added to Widget to log constructor, destructor and assignment operator calls. Interleaves are comments about where those calls come from

w is constructed
0x7fff619c36c0: [constructor] allocated 2@0x1043dff80
temporary Widget(1) is constructed
0x7fff619c37c0: [constructor] allocated 1@0x1043e0180
first (right) assignment operator argument is constructed. w is empty afterwards!
0x7fff619c3800: [default constructor] empty
0x7fff619c3800: [move constructor] stealing 2@0x1043dff80 from 0x7fff619c36c0, replacing with 0@0x0
first assignment operator does it's job, i.e. moves from by-value argument.
0x7fff619c37c0: [assignment] stealing 2@0x1043dff80 from 0x7fff619c3800, replacing with 1@0x1043e0180
second (left) assignment operator arguments is constructed
0x7fff619c3780: [constructor] allocated 2@0x1043e0280
0x7fff619c3780: [copy constructor] copying 2@0x1043dff80 from 0x7fff619c37c0
second assignment operator does it's job, i.e. moves from by-value argument
0x7fff619c36c0: [assignment] stealing 2@0x1043e0280 from 0x7fff619c3780, replacing with 0@0x0
second assingment operator's by-value argument is destructed
0x7fff619c3780: [destructor] deleting 0@0x0
first assignment operator's by-value argument is destructed
0x7fff619c3800: [destructor] deleting 1@0x1043e0180
temporary created as Widget(1) is destructed.
0x7fff619c37c0: [destructor] deleting 2@0x1043dff80
data contains in "w" after assignments.
deadbeef
finally, "w" is destructed.
0x7fff619c36c0: [destructor] deleting 2@0x1043e0280

I can see no problem there, and compiling this with clang and -faddress-sanitizer, -fcatch-undefined-behaviour doesn't complain either.

Note, though, that the second assigment (the left = operator) copies instead of moving. This is because the first (right) assignment operator returns an lvalue-reference.

fgp
  • 8,126
  • 1
  • 17
  • 18
  • OK you are right this may be compiler problem , because the there is no logical error, although clag doesn't issue any compliant I see that we must prevent this nonsensical way of assignments sequences thank you very mush fgp and thank you for all. – Abdulrhman Oct 02 '12 at 06:08
  • @Abdulrhman what compiler are you using? Maybe your compiler get's the initialization of `length_` and `data_` wrong. You could try to initialize them explicitly in the move constructor, instead of relying on the compiler to use the initializer from the declaration. BTW, please consider accepting the answer that contains the actual code, instead of this followup-answer. That's going to be less confusing for others who look at this question. – fgp Oct 02 '12 at 10:17
0

You don't need so many swaps and assignments in your move constructor. This:

Widget(Widget&& other) :
    length( other.length_ ), data( other.data_ )
{
    other.length_ = 0;
    other.data_ = nullptr;
}

does the minimum work for the move constructor: 4 assignments in total. Your version had 8, counting the ones in the calls to swap().

Your move assignment is OK, but you might want to consider just writing one operator=() to cover both cases:

Widget &operator=( Widget other ) {
    delete data_;
    data_ = other.data_;
    other.data_ = nullptr;
    length_ = other.length_;
    other.length_ = 0;
    return *this;
}

This is slightly less efficient than your version, in that it can move twice.

Brangdon
  • 627
  • 5
  • 7