将右值引用分配给左值时发生了啥?

Posted

技术标签:

【中文标题】将右值引用分配给左值时发生了啥?【英文标题】:What happened when assigning an rvalue reference to lvalue?将右值引用分配给左值时发生了什么? 【发布时间】:2019-10-14 11:32:27 【问题描述】:

我有一个简单的例子:

void Message::move_Folders(Message *m) 
    m_folders = std::move(m->m_folders);
    for (auto f : m_folders) 
        f->removeMessage(m);
        f->addMessage(this);
    
    m->m_folders.clear();

此功能可以将“文件夹”成员从一个移动到另一个。所以我想知道:我需要为“文件夹”提供移动分配功能吗?

folders &operator=(folders &&f) 
    ...

将右值引用分配给左值时发生了什么?还是复制操作吗? (我相信不会。)

注意:文件夹是一组文件夹对象。一条消息可能属于许多文件夹。所以一条消息包含一组文件夹。该文件夹还有一个成员messages。一个文件夹可能包含许多消息。有点复杂。

这是MessageFolder的定义:

class Message 
public:
// constructor and destructor
...
private:
    std::set<Folder *> m_folders;
...
;

class Folder 
friend class Message;
//constructor and destructor
...
private:
    std::set<Message *> m_messages;
;

谢谢

【问题讨论】:

在你的例子中 folders 是一个成员变量和一个类型,这相当混乱 文件夹是一组文件夹对象。一条消息可能属于许多文件夹。所以一条消息包含一组文件夹。 @ZhenYang 请在问题中包含folders 的定义,我认为这不会引起混淆。请阅读minimal reproducible example @RichardCritten 我已经添加了消息和文件夹的定义。我认为 std::set 已经执行了移动分配。你认为是吗?但是 m->m_folders.clear();正如 xtofl 所说,没有必要。 folders &amp;operator=(folders &amp;&amp;f) 应该是 Folder&amp; operator=(Folder&amp;&amp; f) ,这就是最初引起我困惑的原因 【参考方案1】:

注意std::move 不会移动任何东西;相反,它使事物可移动。因此,为了将m-&gt;folders 实际移动到this-&gt;folders 中,移动赋值确实必须执行该逻辑。

事实上,移动赋值应该是负责定义移动语义的那个:move_folders 方法不必清除 m-&gt;folders,因为它不应该知道这个细节。

【讨论】:

我错过了一些东西。 “文件夹”是一组“文件夹”。这是一个 std::set。所以表达式:'folders = std::move(m->folders);'将调用 std::set 移动赋值来移动。我不需要做任何事情。对吗? 没错。实际上错过了这个事实。我的猜测是集合移动分配确实清除了传入的参数。不过,我不确定这是否有保证。另一方面,这应该没关系。 请注意,移动后 std::set 处于未指定的有效状态,您可以在没有任何先决条件的情况下对其调用方法,但如果您想继续使用它,例如在其中插入项目,然后您需要调用 clear 将其设置为指定状态。 请注意,尽管folders 当前是std::set,但“直接在代码中表达想法”指南提示您应该为其创建一个类(为方便起见,可以使用set )。参照。 isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rp-direct【参考方案2】:

是否需要提供用户定义的移动分配取决于您已经定义了哪些其他特殊成员,请参见图片了解组合:

因此,如果 Folders 没有用户定义的析构函数、复制构造函数或赋值运算符,则移动构造函数和赋值将由编译器隐式声明。否则你需要自己写一个。

将右值分配给左值时发生了什么?还是复制操作吗? (我相信不会。)

这取决于你是否有一个移动分配,它会移动,否则它会复制。

最后你是否需要为你的 Folder 类移动赋值来移动 std::set 实际上是否,因为 std::set 的移动赋值不会使用默认分配器一一移动内容。

【讨论】:

是的,我明白了。但是画面有点复杂。我需要时间来理解它。 信用到期。图片来自哪里? Howard Hinnant 在 ACCU 2014 会议上的演讲“你想知道的关于移动语义(以及一些)的一切”【参考方案3】:

您不需要提供移动分配运算符,但如果您不这样做,调用确实会降级为副本。

【讨论】:

是的。如果文件夹不是 std::set,它将是一个副本。

以上是关于将右值引用分配给左值时发生了啥?的主要内容,如果未能解决你的问题,请参考以下文章

将右值引用绑定到(自动生成的)左值

左值与右值引用 详解

深入思考右值引用

如何评价 C++11 的右值引用(Rvalue reference)特性?

左值左值引用右值右值引用

C++左值左值引用右值右值引用