默认移动构造

Default move construction

本文关键字:移动 默认      更新时间:2023-10-16

《C++编程语言》一书中有一个关于默认操作的例子。我的问题集中在默认移动操作上。

struct S {
    std::string a;
    int b;
};
S f(S arg) {
    S s0{};
    S s1(s0); //s1 {s0}; in the book
    s1 = arg;
    return s1;
}

之后它说:

s1的复制构造复制s0.a和s0.b。s1的返回移动s1.a和s1.b,使s1.a为空字符串,s1.b保持不变。请注意,内置类型的moved-from对象的值是不变的。这是编译器要做的最简单、最快的事情。

我想这意味着如果我写:

S s3{"tool",42};
f(s3);

由于s1的值被移动,s1.a将返回到",而s1.b不变?那么当f()执行完毕,s1会被销毁吗?我正试图找到一种方法来测试我的猜测,但在函数执行后,我找不到知道s1值的方法,因为它当然是本地的。我写了一个析构函数,只是为了在值被破坏之前找到它们。

~S() {
    std::cout << a << " " << b << 'n';
}

输出为:

0 //values of s0?
tool 42
tool 42
tool 42

看来我的猜测完全错了,我完全不懂课文。有人能更清楚地解释引文中的文字吗?

首先,通过定义析构函数,您禁用了隐式移动构造函数。你必须添加到你的代码:

S(const S&) = default;
S(S&&) = default;
S& operator=(const S&) = default;
S& operator=(S&&) = default; // not required here but should be added for completeness

然后,无论如何,RVO开始发挥作用。正如其他答案中所指出的,编译器可以省略对复制和移动构造函数的调用。在GCC和clang中,您可以通过添加-fno-elide-constructors编译器选项来禁用此功能。之后你会得到这个输出:

 42 // moved s1 (this can theoretically be different, because the value of s1.a is unspecified)
 0  // s0
tool 42 // arg
tool 42 // return value of f()
tool 42 // s3

演示

s1.a将返回""

也许吧。根据move构造函数的规范,它"处于具有未指定值的有效状态"。通常,如果字符串使用动态分配的数组,并且两个字符串都使用兼容的分配器,则move将转移数组的所有权,使旧字符串为空。但是,如果字符串使用"短字符串优化",即短字符串存储在string对象内部以节省动态内存分配的成本,那么保持旧字符串不变会更快。

并且CCD_ 5是不变的

是的。从指定时,基本体类型永远不会修改。

那么当f()执行完毕时,s1会被销毁吗?

可能不会。除非你有一个非常原始的编译器或故意禁用优化,否则这里可能会使用移动省略的优化(有时称为"返回值优化")。s1将在调用方的堆栈帧中创建,因此无需返回它

在函数执行后,我找不到知道s1值的方法

不,返回后无法检查对象,因为它已不存在。如果你想看看移动的效果,你可以移动到一个新的局部变量

S s2 = std::move(s1);

然后检查CCD_ 9。或者你可以编写自己的移动构造函数

S(S && other) : a(std::move(other.a)), b(b) {
    std::cout << other.a << 'n';
}

但是,如上所述,这可能不会用于函数返回值。