编写移动副本和移动赋值构造函数的有效方法

时间:2012-09-29 08:09:12

标签: c++ constructor c++11 variable-assignment move

以下分配和复制移动构造函数是否最有效? 如果有人有其他方式请告诉我? 我的意思是什么回合std :: swap?并通过复制构造函数调用赋值在下面的代码中是安全的吗?

#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;
}

3 个答案:

答案 0 :(得分:5)

从效率POV看起来很好,但包含大量重复的代码。我最好

  • 为您的班级实施swap()运算符。
  • 初始化length_data_声明它们的位置。
  • 尽可能以其他操作实施操作。

可能想要使用std::memcpy而不是std::copy,因为您无论如何都在处理原始数组。有些编译器会为你做这件事,但可能不是全部......

这是代码的重复数据删除版本。请注意,只有一个位置需要知道如何交换两个Widget实例。并且只有一个的地方知道如何分配给定大小的Widget。

编辑:您通常还希望使用与参数相关的查找来定位交换,以防您有非原始成员。

编辑: Integrated @ Philipp建议让赋值运算符按值获取参数。这样,它充当移动分配和复制赋值运算符。在移动的情况下,如果你传递一个临时的,那么将不会被复制,因为移动构造函数复制构造函数将用于传递参数。

编辑: C ++ 11允许在rvalues上调用非成本成员,以便与之前版本的标准兼容。这允许编译奇怪的代码,如Widget(...) = someWidget。通过在声明之后放置operator=来阻止this&需要左值delete[]。请注意,即使没有这个限制,代码也是正确的,但它似乎是一个好主意,所以我添加了它。

编辑:正如Guillaume Papin所指出的,析构函数应该使用delete而不是普通new []。 C ++标准要求通过delete []删除通过new' and分配的内存,即允许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_); } new []`使用不同的堆。

{{1}}

答案 1 :(得分:0)

答案是回应@ Abdulrhman在上述评论中的抱怨,即某些(模糊的)作业序列失败了。提出一个单独的答案,因为它更具可读性。

投诉是

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

崩溃。这是我从

获得的输出
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;

将一些代码添加到Widget以记录构造函数,析构函数和赋值运算符调用。交错是关于这些来电来自哪里的评论

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

我可以看到没有问题,用clang和-faddress-sanitizer, -fcatch-undefined-behaviour编译它也不会抱怨。

但请注意,第二个分配(左=运算符)复制而不是移动。这是因为第一个(右)赋值运算符返回左值引用。

答案 2 :(得分:0)

您的移动构造函数中不需要那么多交换和赋值。这样:

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

完成移动构造函数的最小工作:总共4个赋值。你的版本有8,计算调用swap()的那些。

您的移动分配没问题,但您可能只想编写一个operator =()来涵盖这两种情况:

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

效率略低于您的版本,因为它可以移动两次。