我试图证明 move 构造函数在消除不必要的复制方面的有用性。 但是,当我在 Release 中运行时,Visual Studio 优化器会忽略拷贝。当 move 构造函数不可用时不会调用复制构造函数,显然添加一个时不会调用 move 构造函数。
我可以通过在 Debug 中运行来移除优化,但这并不能很好地证明需要 move 语义。
struct A {
int *buff;
A() {
cout << "A::constructor\n";
buff = new int[1000000];
}
A(const A& a) {
cout << "A::copy constructor\n";
buff = new int[1000000];
memcpy(buff, a.buff, 1000000*sizeof(int));
}
A(A&& original)
{
cout << "Move constructor" << endl;
buff = original.buff;
original.buff = nullptr;
}
~A() { cout << "A::destructor\n"; delete[] buff; }
};
A getA()
{
A temp;
temp.buff[0] = 2;
return temp;
}
void useA(A a1) {
cout << a1.buff[0] << endl;
}
void main() {
useA(getA()); // i'd like copy-constructor to be called if no move constructor is provided
}
我可以在代码中更改什么以防止优化器取消复制,并说明添加 move 构造函数如何防止完整复制?
编辑:演示最好不需要明确的 move 调用/施法。
最佳答案
您遇到了命名返回值优化 (NRVO),这不是强制性的,并且要求复制/move ctor 可用。
为了演示零/三/五规则、 move 语义、RVO、复制省略,我将使用以下示例:
struct A{
char* large_buffer = new char[1024];
~A(){ delete[] large_buffer;}
};
struct D{
A a;
int b;
int* c;
};
D construct_D()
{
// Obtained somewhere
A a = {};
int b = 10;
int* c = new int[1024];
// Now we want to construct `D` which should "consume" the values.
D d{a,b,c};
// Perhaps do something with that before returning it.
return d;
}
int main(){
D dd = construct_D();
}
你现在可以解释了:
b
被复制,没关系。A
被破坏,因为天真的复制 ctor 是成员明智的,并且会发生双重删除。 (如果不忘记添加D::~D
)- 可以通过三规则来解决。
- 使用规则五可以更有效,因为在构造
d
之后永远不会使用a
并且如果它可以放弃其缓冲区并被“消耗”会更好. - 解释
std::move
的作用,以实现这种消费。您可以交互地重写代码以将一些变量更改为临时变量并深入研究 r-value 语义。 - 编写五个函数很乏味,将
char*
替换为std::vector
可以获得零规则,这很好。 c
复制指针,而不是缓冲区,这在这种情况下是可取的,但很危险,因为有人必须稍后释放它以及std::unique_ptr
如何解决这个问题,或std::string
,或再次std::vector
。- 现在有些学生可能会问
D
是否需要 0/3/5 以及我们如何解决这个问题。 - 您可以添加 3/5 并观察没有进行这些调用并解释 NVRO 和 RVO 的
return {a,b,c}
以及随后的dd
构造>. - 还有人总是会用
D dd
不调用赋值运算符来混淆某些人。 - 最后你可以争辩说
construct_D
太长了,所以你会交互地将它重构为更小的函数,同时使用所有学到的概念 -std::move
,通过引用,值(value),RVO。
总的来说,您可以在这个示例上花费一两个小时,另外还可以传播 std::
宣传关于 new
和原始指针的邪恶.
关于c++ - 演示 move 构造函数的有用性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71725173/