C++11:调用移动 ctor/operator= 时

C++11: when move ctor/operator= is called?

本文关键字:operator ctor 调用 移动 C++11      更新时间:2023-10-16
#include <iostream>
class Class
{
    public:
    Class() { std::cerr << "ctor" << std::endl; }
    ~Class() { std::cerr <<"dtor" << std::endl; }
    Class(Class&)  { std::cerr << "copy ctor" << std::endl; }
    Class & operator=(const Class &)
    {
            std::cerr << "copy operator=" << std::endl;
            return *this;
    }
    Class(Class&&) { std::cerr << "move ctor" << std::endl;}
    Class & operator=(Class &&)
    {       
            std::cerr << "move operator="<< std::endl;
            return *this;
    }
 };
int main(int, char**)
{
    Class object;
    Class && rvr = Class(); 
    object = rvr; // (*)
}

输出为

ctor
ctor
copy operator=
dtor
dtor

1) 为什么在第 (*) 行调用"复制 ctor"?

2)如果我每次都必须使用std::move(),那么"移动语义"和任何移动数据的方法(如object.destructive_move();)有什么区别?

3) 何时调用移动 CTOR/操作员?

谢谢!

复制 ctor 不是在第 (*) 行调用的,而是调用复制赋值运算符。我想这就是你的意思。

调用的是复制赋值运算符,而不是移动赋值运算符,因为rvr是左值。请注意,表达式的类型与它是否为左值正交。

如果将赋值语句修改为 object = Class(); 甚至 object = static_cast<Class&&>(rvr) ,您会发现正在调用移动赋值运算符,因为在这两种情况下 RHS 都是右值。

这种行为是明智的:例如,考虑移动分配运算符的实现。它的参数具有右值引用类型,但它仍然是左值。如果它是右值,那么第一次使用该参数可能会修改其状态以成为"空"对象(移动语义),然后第二次使用该参数可能无法按预期工作。

它的实际工作方式是,当您想要以使其处于"空"状态的方式使用该参数时,您将显式使用 std::move

Class && rvr = Class(); 

我要说的是没有意义,但这是C++11规则。

rvr是一个变量,它是对Class的右值引用。这很容易理解。但重要的是要理解这一点:命名变量从来都不是右值表达式。即使它是右值引用!

同样,我知道这没有意义,但这是规则。右值引用变量不是右值表达式

临时是一个右值表达式,因此Class()为您提供右值。从函数返回的临时函数也是一个右值表达式,因此,如果您有一些按值返回Class函数,您将获得一个右值。

这很重要,因为如果您有两个基于左值和右值引用的函数重载(如复制/移动构造函数/赋值),则仅当表达式类型为右值时,C++11 才会选择右值引用版本。

这就是std::move存在的原因。它的存在是为了明确说明何时要移动某些内容。它通过将您赋予它的值转换为右值表达式来实现此目的。也就是说,它返回Class&& .

这是真正令人困惑的部分。如果您有命名的右值引用变量,则不是右值表达式。但是,如果您有未命名的右值引用,例如从函数返回Class&&,那就是右值表达式

std::move返回Class&& .因此,如果要从命名变量移动,则必须始终对其调用std::move。因此,如果要从 rvr ,则必须执行以下操作:

object = std::move(rvr); // (*)

这将返回Class&&rvr。将发生C++11过载解决方案。由于要operator=的参数是右值表达式,因此它将选择operator=的右值引用版本。从而调用一个动作。

请注意,std::move实际上只是对半复杂演员表的包装。它不做移动;是operator=在做移动。