以下分配和复制移动构造函数是否最有效? 如果有人有其他方式请告诉我? 我的意思是什么回合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;
}
答案 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;
}
效率略低于您的版本,因为它可以移动两次。