C ++直接列表初始化与删除的默认构造函数

时间:2017-03-08 04:41:09

标签: c++ c++17

我有以下类定义。我包含了一个私有x,以确保它不是聚合。

class A {
 private:
  int x;

 public:
  A() = delete;
  A(std::initializer_list<int>) { printf("init\n"); }
};

现在,如果我使用A a = A{}初始化此对象,则会删除A::A()。我想这是试图调用A::A()但它被删除了。如果我注释掉该行,则会自动生成A::A()。然后,如果我运行此代码,我可以看到它正在调用A::A(std::initializer_list<int>)

更令人困惑的是,如果我定义A() = default,初始化会再次调用A::A()

有人能指出我正确的方向来理解这种行为吗?谢谢!

我正在使用带有标记-std=c++17的G ++ 6.3.0。

3 个答案:

答案 0 :(得分:2)

行为是正确的。

首先:

// Calling
A a = A{}; // equals A a = A();, 

这是列表初始化习惯用法的便利统一。 请参阅:https://stackoverflow.com/a/9020606/3754223

案例A:

class A {
     private:
         int x;

     public:
         **A() = delete;**
         A(std::initializer_list<int>) { printf("init\n"); }
};

如上所述,A a = A {}将是...... = A(),将被删除。

<案例B:
class A {
     private:
         int x;

     public:
         A(std::initializer_list<int>) { printf("init\n"); }
};

在这种情况下,没有提供默认构造函数,因为您已定义了初始化列表构造函数。因此,{}被隐含地转换为空的初始化列表!

<案例C:
class A {
     private:
         int x;

     public:
         **A() = default;**
         A(std::initializer_list<int>) { printf("init\n"); }
};

在这种情况下,空的默认构造函数可用,A {}被转换回A(),导致它被调用。

请参阅以下页面并仔细阅读。 http://en.cppreference.com/w/cpp/utility/initializer_list http://en.cppreference.com/w/cpp/language/value_initialization

最后:

使用:

A a = {{}};

会导致始终调用初始化列表构造函数,因为外部花括号表示init-list,而内部是零构造元素。 - &GT;非空初始化列表...(再次参见上面的stackoverflow链接!)

顺便说一句,我无法理解为什么这个问题被贬低,考虑到这是一个非常棘手的部分......

答案 1 :(得分:2)

你列出了很多案例,所以让我们一个接一个地讨论。

A() = delete;
A(std::initializer_list<int>) { ... }

A a = A{};确实会尝试调用已删除的默认构造函数,而您的代码无法编译。

您上面的内容是list initialization,因为braced-init-list is emptyvalue initialization会被执行。这将选择已删除的默认构造函数,这会使您的代码格式不正确。

  

如果我注释掉该行,那么A::A()会自动生成

不,事实并非如此。由于存在使用initializer_list的用户提供的构造函数,因此没有隐式声明的默认构造函数。现在,A a = A{};将调用initializer_list构造函数,在这种情况下,initializer_list将为空。

  

如果我定义A() = default,初始化再次调用A::A()

这与第一种情况相同,只是默认构造函数显式为default而不是delete d,因此您的代码会编译。

最后,

  

我添加了私有x以确保它不是聚合

没有必要这样做,定义构造函数会使A成为非aggregate

答案 2 :(得分:1)

列表初始化遵循非常具体的顺序,如[dcl.init.list]/3中所述。突出显示了两个相关的要点及其相对顺序:

  

类型T的对象或引用的列表初始化定义如下:
   - 如果T是一个聚合类和[...]
   - 否则,如果T是字符数组且[...]
   - 否则,如果T是聚合,[...]
   - 否则,如果初始化列表没有元素且T是具有默认构造函数的类类型,则该对象是值初始化的。
   - 否则,如果Tstd::initializer_list<E>的特化,[...]
   - 否则,如果T是类类型,则考虑构造函数。枚举适用的构造函数,通过重载决策选择最佳构造函数(13.3,13.3.1.7)。
   - 否则,如果初始化列表包含E和[...]类型的单个元素    - 否则,如果T是参考类型,[...]
   - 否则,如果T是具有固定基础类型(7.2)的枚举,[...]
   - 否则,如果初始化列表没有元素,则对象进行值初始化    - 否则,该程序格式不正确。

具有默认构造函数的类类型的空初始值设定项列表是在正常构造函数解析之前的特殊情况。您的类不是聚合(因为它具有用户提供的构造函数),因此如果A{}具有默认构造函数,A将尝试对A进行值初始化。如果默认构造函数可访问并不重要,只有存在才有意义。如果它存在且无法访问(您的第一个案例),那就是格式错误。如果它存在且可访问(您的第三种情况),则调用它。只有当您的类没有默认构造函数时,才会枚举构造函数,在这种情况下,将使用空的初始化列表(第二种情况)调用A(initializer_list<int> )构造函数。