c++ - 演示 move 构造函数的有用性

标签 c++ move-semantics move-constructor copy-elision

我试图证明 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();
}

你现在可以解释了:

  1. b 被复制,没关系。
  2. A 被破坏,因为天真的复制 ctor 是成员明智的,并且会发生双重删除。 (如果不忘记添加D::~D)
  3. 可以通过三规则来解决。
  4. 使用规则五可以更有效,因为在构造 d 之后永远不会使用 a 并且如果它可以放弃其缓冲区并被“消耗”会更好.
  5. 解释 std::move 的作用,以实现这种消费。您可以交互地重写代码以将一些变量更改为临时变量并深入研究 r-value 语义。
  6. 编写五个函数很乏味,将 char* 替换为 std::vector 可以获得零规则,这很好。
  7. c 复制指针,而不是缓冲区,这在这种情况下是可取的,但很危险,因为有人必须稍后释放它以及 std::unique_ptr 如何解决这个问题,或 std::string,或再次 std::vector
  8. 现在有些学生可能会问 D 是否需要 0/3/5 以及我们如何解决这个问题。
  9. 您可以添加 3/5 并观察没有进行这些调用并解释 NVRO 和 RVO 的 return {a,b,c} 以及随后的 dd 构造>.
  10. 还有人总是会用 D dd 不调用赋值运算符来混淆某些人。
  11. 最后你可以争辩说 construct_D 太长了,所以你会交互地将它重构为更小的函数,同时使用所有学到的概念 - std::move,通过引用,值(value),RVO。

总的来说,您可以在这个示例上花费一两个小时,另外还可以传播 std:: 宣传关于 new 和原始指针的邪恶.

关于c++ - 演示 move 构造函数的有用性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71725173/

相关文章:

c++ - 什么是 std::move(),什么时候应该使用它?

c++ - 为什么调用复制构造函数而不是 move 构造函数?

c++ - std::vector 不使用具有 noexcept move 构造的对象调用 move 构造函数

c++ - C3646和C4430是否由于头文件层次结构?

python - 如何使用嵌入式 python 解释器构建和执行 cpp 文件(pybind11)

c++ - 返回到转换构造函数中的仅 move 类型

c++ - 移动构造函数会自动初始化未列出的成员吗?

c++ - 辅助函数应该放在头文件中还是放在实现文件中?

c++ - 如何将 python 类包装到 C++ 类中?

c++ - 自动 xvalue 优化