为什么添加析构函数会更改此结构的复制构造函数行为?

Ben*_*Ben 19 c++ c++11

我有一些代码让我感到困惑.特别是,当我尝试将某些内容添加到列表作为初始化列表时 - 它一直有效,直到我添加析构函数 - 然后它开始尝试查找复制构造函数.

这似乎并不是完全一致的行为.拿这个最小的例子:

#include <list>
int main()
{
    class MemberType
    {
    public:
        MemberType()  {}
        MemberType(MemberType&& copy) { }
    };
    struct ListItemType
    {
        MemberType x;
        ~ListItemType() {}
    };
    std::list<ListItemType> myList;
    myList.push_back({MemberType()});
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这无法在GCC和VS2015中编译,因为push_back尝试访问ListItemType复制构造函数:

main()::ListItemType::ListItemType(const main()::ListItemType&)
Run Code Online (Sandbox Code Playgroud)

(根据我的理解).这似乎是有道理的,因为列表push_back将复制(因为没有移动构造函数),除非这不是行为,如果您删除析构函数.注释掉析构函数,编译按预期成功.

也就是说,即使使用析构函数,以下工作也很好 - 不需要复制或移动构造函数来满足它.这对我来说似乎是一样的行为.

ListItemType foo = { MemberType() };
Run Code Online (Sandbox Code Playgroud)

最后,如果删除或注释掉移动构造函数MemberType- 编译再次成功 - 意味着以下将编译.

#include <list>
int main()
{
    class MemberType
    {
    public:
        MemberType()  {}
    };
    struct ListItemType
    {
        MemberType x;
        ~ListItemType() {}
    };
    std::list<ListItemType> myList;
    myList.push_back({MemberType()});
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

有人可以解释一下这里的行为吗?为什么push_back尝试访问复制构造函数ListItemType- 但只有ListItemType具有析构函数并且MemberType具有移动构造函数?

don*_*mus 27

您正在观察的行为是由管理编译器生成隐式复制或移动构造函数的规则生成的:

隐含移动

如果未定义,则在以下情况下为类隐式声明移动构造函数:

  • 该类没有用户定义的副本构造函数; 和
  • 该类没有用户定义的副本分配或移动赋值运算符; 和
  • 该类没有用户定义的析构函数.

隐式复制

如果未定义,则在以下情况下隐式删除类的复制构造函数:

  • 该类具有用户定义的移动构造函数; 要么
  • 其他与此无关的原因......

在您的问题中,您有几种情况:

情况1

  • ListItemType 有一个析构函数
  • MemberType 有一个移动构造函数

ListItemType由于存在析构函数,隐式移动构造函数已被删除.因此push_back必须使用复制构造函数将其ListItemType放入列表中.

在这种情况下,复制构造函数ListItemType不能被隐式声明为其数据成员之一(MemberType)包含一个移动构造函数,它可以防止MemberType生成隐式复制构造函数.

案例2

  • ListItemType 没有析构函数
  • MemberType 有一个移动构造函数

可以隐式生成移动构造函数,ListItemType并将其用于将值移动到列表中.

案例3

  • ListItemType 有一个析构函数
  • MemberType 没有移动构造函数

对于隐式的拷贝构造函数ListItemTypeMemberType可以产生和使用的值复制到列表中.

最后,表达式ListItemType foo = { MemberType() };是聚合初始化并遵循不同的规则.在任何一种情况下MemberType都将具有足以进行聚合初始化的移动或复制构造函数.