C++ 通过调用Move赋值运算符实现Move构造函数
MSDN文章有以下建议 如果为类提供移动构造函数和移动赋值运算符,则可以通过编写 移动构造函数调用移动赋值运算符。这个 下面的示例显示了move构造函数的修订版本 调用移动分配操作符:C++ 通过调用Move赋值运算符实现Move构造函数,c++,c++11,move-semantics,move-constructor,C++,C++11,Move Semantics,Move Constructor,MSDN文章有以下建议 如果为类提供移动构造函数和移动赋值运算符,则可以通过编写 移动构造函数调用移动赋值运算符。这个 下面的示例显示了move构造函数的修订版本 调用移动分配操作符: 通过双重初始化MemoryBlock的值,这段代码是否效率低下,或者编译器是否能够优化掉额外的初始化?我是否应该始终通过调用move赋值操作符来编写move构造函数?我认为您不会注意到显著的性能差异。我认为从移动构造函数中使用移动赋值操作符是不错的做法。< /P> 但是,我宁愿使用std::forward而不是s
通过双重初始化
MemoryBlock
的值,这段代码是否效率低下,或者编译器是否能够优化掉额外的初始化?我是否应该始终通过调用move赋值操作符来编写move构造函数?我认为您不会注意到显著的性能差异。我认为从移动构造函数中使用移动赋值操作符是不错的做法。< /P>
但是,我宁愿使用std::forward而不是std::move,因为它更符合逻辑:
*this = std::forward<MemoryBlock>(other);
*this=std::forward(其他);
我不会这样做。移动成员首先存在的原因是性能。为你的行动做这件事就像花巨资买一辆超级汽车,然后试图通过购买普通汽油来省钱
如果您想减少编写的代码量,就不要编写移动成员。在移动上下文中,您的类可以很好地复制
如果您希望您的代码具有高性能,那么可以调整移动构造函数和移动分配,使其尽可能快。好的移动成员会非常快,你应该通过计算负载、存储和分支来估计他们的速度。如果你可以用4个加载/存储而不是8个来写东西,那就去做吧!如果你可以写一些没有分支而不是1的东西,那就去做吧
当你(或你的客户)将你的类放入一个std::vector
中时,你的类型会产生大量的移动。即使你的移动速度在8次加载/存储时是闪电般的快,如果你能在4次或6次加载/存储时使速度提高两倍,甚至快50%,那么这是值得花费的时间
就我个人而言,我讨厌看到等待的游标,我愿意多花5分钟来编写我的代码,并且知道这是尽可能快的
如果您仍然不相信这是值得的,请用两种方法编写它,然后在完全优化时检查生成的程序集。谁知道呢,您的编译器可能足够聪明,可以为您优化掉额外的负载和存储。但此时您已经投入了比刚编写优化移动构造函数更多的时间。这取决于移动赋值操作符的操作。如果您查看链接到的文章中的内容,您会看到部分内容:
// Free the existing resource.
delete[] _data;
因此,在此上下文中,如果您从移动构造函数调用移动赋值运算符,而不首先初始化
\u data
,那么您将尝试删除未初始化的指针。因此,在本例中,无论是否低效,初始化值实际上是至关重要的。MyC++11版本的MemoryBlock
类
#include <algorithm>
#include <vector>
// #include <stdio.h>
class MemoryBlock
{
public:
explicit MemoryBlock(size_t length)
: length_(length),
data_(new int[length])
{
// printf("allocating %zd\n", length);
}
~MemoryBlock() noexcept
{
delete[] data_;
}
// copy constructor
MemoryBlock(const MemoryBlock& rhs)
: MemoryBlock(rhs.length_) // delegating to another ctor
{
std::copy(rhs.data_, rhs.data_ + length_, data_);
}
// move constructor
MemoryBlock(MemoryBlock&& rhs) noexcept
: length_(rhs.length_),
data_(rhs.data_)
{
rhs.length_ = 0;
rhs.data_ = nullptr;
}
// unifying assignment operator.
// move assignment is not needed.
MemoryBlock& operator=(MemoryBlock rhs) // yes, pass-by-value
{
swap(rhs);
return *this;
}
size_t Length() const
{
return length_;
}
void swap(MemoryBlock& rhs)
{
std::swap(length_, rhs.length_);
std::swap(data_, rhs.data_);
}
private:
size_t length_; // note, the prefix underscore is reserved.
int* data_;
};
int main()
{
std::vector<MemoryBlock> v;
// v.reserve(10);
v.push_back(MemoryBlock(25));
v.push_back(MemoryBlock(75));
v.insert(v.begin() + 1, MemoryBlock(50));
}
#包括
#包括
//#包括
类内存块
{
公众:
显式内存块(大小和长度)
:长度(长度),
数据(新整数[长度])
{
//printf(“分配%zd\n”,长度);
}
~MemoryBlock()无例外
{
删除[]数据;
}
//复制构造函数
内存块(常量内存块和rhs)
:MemoryBlock(rhs.length)//委托给另一个ctor
{
标准::副本(rhs.data,rhs.data+长度,数据);
}
//移动构造函数
MemoryBlock(MemoryBlock&&rhs)无例外
:长度(右侧长度),
数据(右侧数据)
{
rhs.长度u0;
rhs.data_u2;=空PTR;
}
//统一赋值运算符。
//不需要移动分配。
MemoryBlock&operator=(MemoryBlock rhs)//是,按值传递
{
掉期(rhs);
归还*这个;
}
大小\u t长度()常数
{
返回长度;
}
无效交换(内存块和rhs)
{
标准::交换(长度,右侧长度);
标准::交换(数据交换,右数据交换);
}
私人:
size\u t length\u;//注意,前缀下划线是保留的。
int*数据;
};
int main()
{
std::向量v;
//五储备(10);
v、 向后推(内存块(25));
v、 向后推(内存块(75));
v、 插入(v.begin()+1,MemoryBlock(50));
}
如果使用正确的C++11编译器,
MemoryBlock::MemoryBlock(size\u t)
在测试程序中只应调用3次。我只需消除成员初始化并编写
MemoryBlock(MemoryBlock&& other)
{
*this = std::move(other);
}
除非移动分配抛出异常,否则这将始终有效,而且通常不会
这种样式的优点:
今天的优化器在优化代码方面做得非常出色。您的示例代码特别容易优化。首先:在几乎所有情况下,move构造函数都是内联的。如果您通过移动分配操作符实现它,那么该操作符也将内联 让我们来看一些例子
MemoryBlock(MemoryBlock&& other)
{
*this = std::move(other);
}