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

时间:2016-06-21 03:18:18

标签: 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;
}

由于push_back尝试访问ListItemType副本构造函数,因此无法在GCC和VS2015中进行编译:

main()::ListItemType::ListItemType(const main()::ListItemType&)

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

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

ListItemType foo = { MemberType() };

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

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

有人可以在这里解释一下这种行为吗?为什么push_back尝试访问ListItemType的副本构造函数 - 但仅当ListItemType具有析构函数且MemberType具有移动构造函数时?

1 个答案:

答案 0 :(得分:27)

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

隐式移动

如果未定义,则移动构造函数对于类隐式声明,如果:

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

隐式复制

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

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

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

案例1

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

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

在这种情况下,ListItemType的复制构造函数不能被隐式声明为其数据成员之一(MemberType)包含移动构造函数,这会阻止MemberType的隐式复制构造函数正在生成。

案例2

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

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

案例3

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

可以生成ListItemTypeMemberType的隐式复制构造函数,并将其用于将值复制到列表中。

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