我已经搜索但找不到“何时”使用它们的答案。我只是听说它很好,因为它为我节省了额外的副本。我把它放在我所拥有的每一堂课中,但有些课程似乎对某些课程没有意义:我读过无数关于LValues和RValues以及std :: move vs. std :: copy vs. memcpy的教程与memmove等等。甚至读取throw(),但我不确定何时使用它。
我的代码如下:
struct Point
{
int X, Y;
Point();
Point(int x, int y);
~Point();
//All my other operators here..
};
然后我有一个类数组(RAII sorta thing):
class PA
{
private:
std::vector<Point> PointsList;
public:
PA();
//Variadic Template constructor here..
~PA();
//Operators here..
};
我应该使用移动构造函数和复制构造函数吗?我在Point Class中有它,但它感觉很奇怪,所以我把它删除了。然后我在PA课上有它,但我认为它不会做任何事情所以我也删除了它。然后在我的bitmaps类中,我的编译器抱怨指针成员但没有过载,所以我做了:
//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);
}
这是对的吗?我做错了什么或错了吗?我需要throw()吗?我的任务和移动任务操作员实际上应该是这样吗?我需要复印作业吗?啊,这么多问题:c我问过的最后一个论坛都无法回答所有问题,所以我感到很困惑。最后我应该使用unique_ptr作为字节吗? (这是一个字节/像素数组。)
答案 0 :(得分:12)
Scott Meyer's blog上有一些很好的想法:
首先,并非所有复制请求都可以被移动替换。只有rvalues的复制请求才有资格进行优化。其次,并非所有类型都支持比复制操作更有效的移动操作。一个例子是std :: array。第三,即使支持高效移动操作的类型也可能仅在某些时候支持它们。例证:std :: string。它支持移动,但是在使用SSO(小字符串优化)实现std :: string的情况下,小字符串移动和复制一样昂贵!
也许,您可以相应地对类型进行分类,然后确定所有需要移动语义。请注意,编译器自动生成移动控制器/赋值运算符存在限制,因此建议您牢记这些。当您明确指定移动成员时,这会有所帮助。
对于没有明确指定移动成员的类,有一些麻烦的地方。还存在显式/隐式删除移动成员的问题,该成员禁止从 rvalues 进行复制。隐藏生成移动成员的一个非常有价值的问题来源可以在Stroustrup的题为To Move or Not to Move的论文中找到。
关于使用移动语义的异常处理,我建议Dave Abraham的帖子Exceptionally Moving。
当我有空的时候,我会尝试用一些例子回到这个答案。希望暂时上述链接可以帮助您入门。
答案 1 :(得分:3)
尽管移动是一个非常好的工具,它允许的速度远远超过速度。通过移动,您移动对象(显然)并且不留任何东西(除了空的尸体,或者更准确地说是默认的构造对象)。这会迫使您在移动对象受到青睐时更仔细地考虑程序的所有权和设计。移动显然迫使您考虑谁拥有该对象以及何时使用,而不是多次访问某些对象或共享。
正如Bjarne Stroustrup先前所述,我们应该停止分享所有内容并在各处指点。如果使用指针使用unique_ptr而不是shared_ptr,除非您绝对想要共享所有权(在许多情况下您不需要)。 Unique_ptr和它的移动(反正完全删除的复制)构造函数是一个很好的例子,应该提供移动和永不复制的对象。
移动很棒并且编写移动构造函数是一个非常好的想法,当msvc赶上并允许删除/默认装饰器在其他编译器生成(复制/分配等)构造函数时更好。尝试访问以前删除的成员的错误在这里非常有用,只是让一些构造函数私有对代码维护者的意图不太明显。如果复制正常但移动首选,编译器将有希望选择在可能的情况下移动(例如,使用vector.push_back进行测试,如果合理的话,使用某些编译器移动或emplace_back,购买即时性能增益),因此即使在复制构造函数对象可以自动选择已定义的移动构造函数来提高性能(很好地忽略了目前肆虐的所有SSO讨论)。 This is a decent answer to peruse
如果您正在寻找更多信息,那么在boost邮件列表上有一些关于移动/复制的优点/缺点以及通过值/引用传递的非常繁重的线程,它们都在谈论类似的问题。
答案 2 :(得分:1)
首要:尽可能使用“ 零规则”。请参阅“ The rule of three/five/zero”和“ C-20”。
因此:您的“怪异”感觉是正确的:Point
和PA
不需要显式的复制/移动运算符。否则,dirkgently和dirvine的答案及其参考文献可以很好地理解,以供更深入的了解。
对于BMPS
,提供显式的移动运算符当然是个好主意。