理解Stroustrup的PPP中除了移动赋值运算符之外为什么还要调用移动构造函数?

问题描述 投票:0回答:1

我正在阅读 Stroustrup 的《编程:C++ 原理与实践》第 4 版,我对一些旨在说明复制构造函数/复制赋值/移动构造函数/移动赋值/析构函数语言功能的代码的输出感到困惑。 以下代码大部分取自书中,并实现了一个简单类型

X
,以便上述运算符(以及构造函数)在调用时输出显式信息。

#include <iostream>
#include <string>
#include <vector>
using std::cout;
using std::string;
using std::vector;

struct X {
    int val;

    void out(const string& s, int nv) {
        cout << this << "->" << s << ":" << val << "(" << nv << ")\n"; 
    }

    X() { out("X()",0); val=0; }

    explicit X(int x) { out("X(int)",x); val=x; }

    X(const X& x) { out("X(X&)", x.val); val=x.val; }

    X& operator=(const X& x) { 
        out("X copy assignment", 0); 
        val=x.val; 
        return *this; }

    X(X&& x) {
        out("X(X&&)",x.val);
        val = x.val;
        x.val = 0; 
    }

    X& operator=(X&& x) {
        out("X move assignment", x.val);
        val = x.val;
        x.val = 0;
        return *this;
    }

    ~X() { out("~X()", 0); }
};

X copy(X a) {
    cout << "copy()\n";
    return a;
}

int main() {
    X loc {4};
    X loc2 {12};
    loc2 = copy(loc);
    cout << "Done\n";
}

运行该程序时看到的输出如下:

0x16d1370bc->X(int):1(4)
0x16d1370b8->X(int):-1834548312(12)
0x16d1370b0->X(X&):1829990608(4)
copy()
0x16d1370b4->X(X&&):1(4)
0x16d1370b8->X move assignment:12(4)
0x16d1370b4->~X():0(0)
0x16d1370b0->~X():0(0)
Done
0x16d1370b8->~X():4(0)
0x16d1370bc->~X():4(0)

我理解这个输出的前四行——它们来自两个局部变量

loc
loc2
的构造函数,来自
copy
值调用的复制构造函数,以及来自
copy
函数体。 我对下一行感到困惑——即对移动构造函数的调用。 我本以为只会看到对移动赋值运算符的调用。 为什么在这里调用移动构造函数(我不认为有任何对象正在被构造?)?这是在呼吁什么?特别是,我没有看到带有移动构造函数打印的内存地址的对象的构造,所以我很困惑该对象来自哪里。

感谢您的帮助,很抱歉我是 C++ 新手。 如果相关的话,我正在用

g++ -Wall -std=c++20
编译这个程序。 使用不同的编译优化级别,输出仍然存在。

c++ c++20 move-semantics
1个回答
1
投票

正如您的输出所示,表达式

loc2 = copy(loc)
涉及复制、移动和赋值:

  1. loc
    用于复制构造
    a
    copy
  2. 参数
  3. a
    中的局部对象
    copy
    用于移动构造
    copy
    的返回值。
  4. copy
    返回值的引用作为
    x
    参数传递给
    X
    的移动赋值运算符

如果我不得不猜测,您不会将

copy
的返回值视为一个不同的对象。 在许多情况下,这是准确的,因为复制删除会删除该特定副本。 如果您返回纯右值(即
return X{42};
)并使用 C++17 或更高版本,则该优化是强制性的,但在返回命名对象时它是可选的,因为在 100% 的情况下这是不可能的。

© www.soinside.com 2019 - 2024. All rights reserved.