c++ - 什么是聚合和POD以及它们如何/为什么是特殊的?




aggregate c++-faq c++11 (5)

你能否详细说明下列规则:

我会尽力:

a)标准布局类必须具有所有具有相同访问控制的非静态数据成员

很简单:所有非静态数据成员都必须是publicprivateprotected 。 你不能有一些public和一些private

他们的推理理由是区分“标准布局”和“非标准布局”的理由。 也就是说,让编译器自由选择如何把东西放入内存中。 这不仅仅是关于vtable指针。

当他们在98年对C ++进行标准化时,他们必须基本预测人们将如何实现它。 虽然他们在各种C ++中有相当多的实现经验,但他们对事物并不确定。 所以他们决定保持谨慎:给编译器尽可能多的自由。

这就是为什么C ++ 98中POD的定义非常严格。 它为C ++编译器提供了大多数类的成员布局的很大自由度。 基本上,POD类型旨在成为特殊情况,这是由于某种原因专门编写的。

当C ++ 11开始工作时,他们对编译器有更多的经验。 他们意识到...... C ++编译器的编写者真的很懒惰。 他们拥有所有这些自由,但他们没有任何事情。

标准布局的规则或多或少是编纂惯例:大多数编译器并不需要做太多的改变来实现它们(除了对应类型特征的某些东西外)。

现在,当涉及public / private ,情况就不同了。 重新排序哪些成员是public private的自由对编译器来说可能很重要,特别是在调试构建时。 而且由于标准布局的要点是与其他语言兼容,所以在调试与发布时不能使布局有所不同。

然后有一个事实,它并没有真正伤害用户。 如果你正在制作一个封装类,那么很可能你的所有数据成员都是private的。 您通常不会公开数据成员完全封装类型。 所以对于那些想要这么做的用户来说,这只会是一个问题,谁需要这个部门。

所以这不是什么大的损失。

b)整个继承树中只有一个类可以有非静态数据成员,

这个的原因回到他们为什么再次标准化标准布局:通常的做法。

当有两个实际存储事物的继承树的成员时, 没有常见的做法。 有些在派生之前放置基类,有些则以其他方式进行。 如果他们来自两个基础类别,您如何订购这些成员? 等等。 在这些问题上编译器存在很大分歧。

另外,归功于零/一个/无穷大的规则,一旦你说你可以有两个类的成员,你可以说你想要的数量。 这需要添加大量的布局规则来处理这个问题。 你必须说明多重继承是如何工作的,哪些类将他们的数据放在其他类之前等等。这是很多规则,对于很少的材料收益。

你不能创建所有没有虚函数和默认构造器标准布局的东西。

并且第一个非静态数据成员不能是基类类型(这可能会破坏别名规则)。

我不能说这个。 我对C ++的别名规则没有足够的教育来真正理解它。 但它与基本成员将与基类本身共享相同地址的事实有关。 那是:

struct Base {};
struct Derived : Base { Base b; };

Derived d;
static_cast<Base*>(&d) == &d.b;

这可能违反了C ++的别名规则。 某种程度上来说。

但是,请考虑一下:能够做到这一点的能力究竟有多有用? 由于只有一个类可以具有非静态数据成员,因此Derived必须是该类(因为它具有作为成员的Base )。 所以Base 必须是空的(数据)。 如果Base是空的, 还有一个基类......为什么它有一个数据成员呢?

由于Base是空的,它没有状态。 所以任何非静态成员函数都会根据它们的参数来做他们所做的事情,而不是它们的this指针。

再一次:没有什么大的损失。

本常见问题解答关于聚合和POD,并涵盖以下内容:

  • 什么是聚合
  • 什么是POD (普通旧数据)?
  • 他们有什么关系?
  • 他们如何以及为何特别?
  • C ++ 11有哪些变化?

如何阅读:

这篇文章相当长。 如果你想了解总量和POD(普通旧数据),请花点时间阅读。 如果您只对聚合体感兴趣,请阅读第一部分。 如果你只对POD感兴趣,那么你必须先阅读聚合的定义,含义和例子,然后你可以跳到POD,但我仍然建议阅读第一部分的全部内容。 聚合的概念对于定义POD很重要。 如果你发现任何错误(甚至是轻微的,包括语法,文体学,格式,语法等),请留下评论,我会编辑。

什么是聚合以及它们为什么是特殊的

来自C ++标准的正式定义( C ++ 03 8.5.1§1

聚合是没有用户声明的构造函数(12.1),没有私有或受保护的非静态数据成员(第11节),没有基类(第10节),也没有虚函数(10.3)的数组或类(第9节) )。

所以,好的,让我们来解析这个定义。 首先,任何数组都是一个聚合。 一个类也可以是一个聚合,如果......等等! 关于结构或工会什么都没有说,他们不可能是聚合? 是的他们可以。 在C ++中,术语class指的是所有的类,结构体和联合体。 所以,当且仅当它满足上述定义中的标准时,类(或结构或联合)才是聚合。 这些标准意味着什么?

  • 这并不意味着一个聚合类不能有构造函数,实际上它可以有一个默认的构造函数和/或一个复制构造函数,只要它们是由编译器隐式声明的,而不是由用户明确声明的

  • 没有私人或受保护的非静态数据成员 。 您可以拥有尽可能多的私有和受保护成员函数(但不包括构造函数)以及任意数量的私有或受保护的静态数据成员和成员函数,并且不违反聚合类的规则

  • 聚合类可以具有用户声明/用户定义的复制赋值运算符和/或析构函数

  • 即使数组是非聚合类类型的数组,也是聚合数据。

现在我们来看一些例子:

class NotAggregate1
{
  virtual void f() {} //remember? no virtual functions
};

class NotAggregate2
{
  int x; //x is private by default and non-static 
};

class NotAggregate3
{
public:
  NotAggregate3(int) {} //oops, user-defined constructor
};

class Aggregate1
{
public:
  NotAggregate1 member1;   //ok, public member
  Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment  
private:
  void f() {} // ok, just a private function
};

你明白了。 现在让我们看看聚合是如何特殊的。 与非聚合类不同,它们可以用大括号{}进行初始化。 这种初始化语法通常为数组所知,我们刚刚了解到这些是聚合。 所以,让我们从他们开始。

Type array_name[n] = {a 1 , a 2 , …, a m };

如果(m == n)
数组的 i 元素用i初始化
否则如果(m <n)
数组的前m个元素用1 ,a 2 ,...,a m初始化,其他n - m元素在可能的情况下进行值初始化 (参见下面的解释)
否则如果(m> n)
编译器会发出错误
else (当n完全没有被指定,就像int a[] = {1, 2, 3};
假设数组(n)的大小等于m,所以int a[] = {1, 2, 3}; 相当于int a[3] = {1, 2, 3};

当标量类型的对象( boolintchardouble ,指针等)被初始化时,这意味着它被初始化为0 (对于boolfalse ,对于double等为0)。 当具有用户声明的默认构造函数的类类型的对象被初始化时,它的默认构造函数被调用。 如果默认构造函数是隐式定义的,那么所有非静态成员都是递归值初始化的。 这个定义不准确,有点不正确,但它应该给你基本的想法。 引用不能被初始化。 例如,如果类没有适当的默认构造函数,则非聚合类的值初始化可能会失败。

数组初始化的例子:

class A
{
public:
  A(int) {} //no default constructor
};
class B
{
public:
  B() {} //default constructor available
};
int main()
{
  A a1[3] = {A(2), A(1), A(14)}; //OK n == m
  A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
  B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
  int Array1[1000] = {0}; //All elements are initialized with 0;
  int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
  bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
  int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
  //the elements in this case are not value-initialized, but have indeterminate values 
  //(unless, of course, Array4 is a global array)
  int array[2] = {1, 2, 3, 4}; //ERROR, too many initializers
}

现在让我们看看如何使用大括号来初始化聚合类。 几乎相同的方式。 我们将按照它们在类定义中出现的顺序(它们都是定义公开的)来初始化非静态数据成员,而不是数组元素。 如果初始化程序的数量少于成员,则其余部分将进行初始化。 如果无法对其中一个未明确初始化的成员进行值初始化,则会出现编译时错误。 如果有更多的初始化程序超出必要,我们也会收到编译时错误。

struct X
{
  int i1;
  int i2;
};
struct Y
{
  char c;
  X x;
  int i[2];
  float f; 
protected:
  static double d;
private:
  void g(){}      
}; 

Y y = {'a', {10, 20}, {20, 30}};

在上面的例子中, yc'a'初始化, yxi110yxi220yi[0]20yi[1]30yf进行值初始化,即用0.0初始化。 受保护的静态成员d根本不会被初始化,因为它是static

聚合联盟的不同之处在于,您可以使用大括号初始化其第一个成员。 我认为,如果你在C ++中足够先进,甚至考虑使用工会(他们的使用可能是非常危险的,必须仔细考虑),你可以自己查看标准中的工会规则:)。

现在我们知道聚合的特殊之处,让我们试着了解对类的限制; 那就是为什么他们在那里。 我们应该明白,使用大括号进行成员初始化意味着该类不过是其成员的总和。 如果存在用户定义的构造函数,则意味着用户需要做一些额外的工作来初始化成员,因此大括号初始化将不正确。 如果存在虚函数,则意味着此类的对象(在大多数实现中)具有指向构造函数中设置的类的所谓vtable的指针,因此,括号初始化不足。 你可以用类似于练习的方式来计算其余的限制:)。

关于总量足够了。 现在我们可以定义一组更严格的类型,例如POD

什么是POD以及它们为什么是特殊的

来自C ++标准的正式定义( C ++ 03 9§4

POD-struct是一个聚合类,它没有类型非POD-struct,非POD-union(或这种类型的数组)或非引用的非静态数据成员,并且没有用户定义的复制赋值运算符,也没有用户定义的析构函数。 同样,POD-union是一个聚合联合,它没有类型非POD-struct,非POD-union(或这种类型的数组)或非引用的非静态数据成员,也没有用户定义的复制赋值运算符并没有用户定义的析构函数。 POD类是一个POD结构或POD结合的类。

哇,这个更难解析,不是吗? :)让我们离开工会(与上面相同的理由),并以更清晰的方式重述:

如果聚合类没有用户定义的复制赋值运算符和析构函数,并且它的非静态成员都不是非POD类,非POD数组或非引用数据类,则它称为POD。

这个定义意味着什么? (我提到POD代表Plain Old Data吗?)

  • 所有的POD类都是聚合,或者换句话说,如果一个类不是聚合,那肯定不是POD
  • 就像结构一样,类也可以是POD,即使这两种情况下的标准术语都是POD结构
  • 就像聚合的情况一样,这个类所具有的静态成员并不重要

例子:

struct POD
{
  int x;
  char y;
  void f() {} //no harm if there's a function
  static std::vector<char> v; //static members do not matter
};

struct AggregateButNotPOD1
{
  int x;
  ~AggregateButNotPOD1() {} //user-defined destructor
};

struct AggregateButNotPOD2
{
  AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};

POD类,POD联合,标量类型和这类类型的数组统称为POD类型。
POD在许多方面都很特殊。 我会举几个例子。

  • POD类与C结构最接近。 与他们不同,POD可以具有成员函数和任意静态成员,但这两者都不会改变对象的内存布局。 所以如果你想编写一个或多或少的可移植的动态库,可以从C甚至.NET中使用,你应该尝试使所有导出的函数都接受并返回POD类型的参数。

  • 非POD类类型的对象的生命周期始于构造函数完成时,并在析构函数完成时结束。 对于POD类,当对象的存储被占用时,生命周期开始,并在释放或重用存储时结束。

  • 对于POD类型的对象,标准保证当你将对象的内容存入char或unsigned char数组,然后将内容memcpymemcpy到对象中时,对象将保持其原始值。 请注意,对于非POD类型的对象没有这样的保证。 此外,您可以安全地复制POD对象与memcpy 。 以下示例假设T是POD类型:

    #define N sizeof(T)
    char buf[N];
    T obj; // obj initialized to its original value
    memcpy(buf, &obj, N); // between these two calls to memcpy,
    // obj might be modified
    memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
    // holds its original value
    
  • goto声明。 正如你所知道的,它是非法的(编译器应该发出一个错误),通过goto从某个变量尚未处于范围内的点跳转到已经处于范围内的点。 此限制仅适用于变量为非POD类型的情况。 在下面的例子中, f() g()是格式不正确的,而g()是格式良好的。 请注意,微软的编译器对这条规则太自由了 - 它只是在这两种情况下发出警告。

    int f()
    {
      struct NonPOD {NonPOD() {}};
      goto label;
      NonPOD x;
    label:
      return 0;
    }
    
    int g()
    {
      struct POD {int i; char c;};
      goto label;
      POD x;
    label:
      return 0;
    }
    
  • 保证在POD对象的开始处不会有填充。 换句话说,如果一个POD-class A的第一个成员是T类型的,那么你可以安全地从A* reinterpret_castT*并获得指向第一个成员的指针,反之亦然。

名单继续下去......

结论

理解POD究竟是什么是很重要的,因为如你所见,许多语言特征对他们的行为有所不同。


C ++ 14发生了什么变化

我们可以参考Draft C ++ 14标准以供参考。

骨料

这在8.5.1节给出了以下定义:

聚合是没有用户提供的构造函数(12.1),没有私有或受保护的非静态数据成员(第11章),没有基类(第10章)和没有虚函数(10.3)的数组或类(第9章) )。

现在唯一的变化是添加类内成员初始化器不会使类成为非聚合类。 因此, C ++ 11的以下示例为具有成员步调初始值设定项的类聚合初始化 :

struct A
{
  int a = 3;
  int b = 3;
};

在C ++ 11中不是聚合,而是在C ++ 14中。 此更改在N3605:成员初始值设定项和汇总项中进行了介绍 ,其中包含以下摘要:

Bjarne Stroustrup和Richard Smith提出了一个关于聚合初始化和成员初始化不能一起工作的问题。 本文提出通过采用史密斯提出的措辞来解决问题,该措辞消除了聚合不能具有成员初始化者的限制。

POD保持不变

POD( 普通旧数据 )结构的定义在第9节中讲述:

POD结构110是一个非联合类,它既是一个普通类又是一个标准布局类,并且没有类型非POD结构,非POD联合(或这种类型的数组)的非静态数据成员。 同样,POD联合是既是平凡类又是标准布局类的联合,并且没有类型非POD结构,非POD联合(或这种类型的数组)的非静态数据成员。 POD类是一个POD结构或POD结合的类。

这与C ++ 11相同。


C ++ 11有哪些变化?

骨料

聚合的标准定义略有改变,但仍然几乎相同:

聚合是一个数组或类(第9章),没有用户提供的构造函数(12.1),对于非静态数据成员(9.2)没有支撑或平等初始化器 ,没有私有或受保护的非静态数据成员第11章),没有基类(第10章),也没有虚函数(10.3)。

好的,什么改变了?

  1. 以前,聚合可能没有用户声明的构造函数,但现在它不能具有用户提供的构造函数。 有区别吗? 是的,有,因为现在你可以声明构造函数并默认它们:

    struct Aggregate {
        Aggregate() = default; // asks the compiler to generate the default implementation
    };
    

    这仍然是一个聚合,因为在第一个声明中默认的构造函数(或任何特殊的成员函数)不是用户提供的。

  2. 现在,聚合不能为非静态数据成员设置任何括号或等于初始值设定项。 这是什么意思? 那么,这只是因为有了这个新标准,我们可以像这样直接在类中初始化成员:

    struct NotAggregate {
        int x = 5; // valid in C++11
        std::vector<int> s{1,2,3}; // also valid
    };
    

    使用此功能使该类不再是聚合,因为它基本上等同于提供您自己的默认构造函数。

所以,什么是一个聚合没有太大改变。 它仍然是一个基本的想法,适应新的功能。

POD呢?

POD经历了很多变化。 这个新标准放宽了很多关于POD的规则,标准中提供的定义方式发生了根本性的变化。

POD的想法是捕捉基本上两个不同的属性:

  1. 它支持静态初始化,并且
  2. 使用C ++编译POD会为您提供与在C编译的结构相同的内存布局。

因此,这个定义被分成两个不同的概念: 普通类和标准布局类,因为它们比POD更有用。 现在这个标准很少使用术语POD,更喜欢更具体的平凡标准布局概念。

新的定义基本上说POD是一个既平凡又具有标准布局的类,并且该属性必须递归地保存所有非静态数据成员:

POD结构是一个非联合类,它既是一个普通类又是一个标准布局类,并且没有类型非POD结构,非POD联合(或这种类型的数组)的非静态数据成员。 同样,POD联合是既是平凡类又是标准布局类的联合,并且没有类型非POD结构,非POD联合(或这种类型的数组)的非静态数据成员。 POD类是一个POD结构或POD结合的类。

我们分别详细讨论这两个属性中的每一个。

平凡的课程

Trivial是上面提到的第一个属性:普通类支持静态初始化。 如果一个类是可复制的(一个普通的类的超集),可以将它的表示复制到像memcpy这样的东西上,并期望结果是相同的。

该标准定义了一个微不足道的类如下:

一个可复制的类是一个类:

- 没有非平凡的拷贝构造函数(12.8),

- 没有不平凡的动作构造函数(12.8),

- 没有非平凡的拷贝分配操作符(13.5.3,12.8),

- 没有不平凡的移动赋值操作符(13.5.3,12.8)和

- 有一个微不足道的析构函数(12.4)。

一个普通的类是一个具有简单的默认构造函数(12.1)的类,并且可以复制。

[ 注意:特别是一个可复制或平凡的类没有虚函数或虚基类。 - 注意 ]

那么,那些微不足道的和不平凡的事情是什么?

类X的复制/移动构造函数如果不是用户提供的,并且如果不是用户提供的,则是微不足道的

- X类没有虚函数(10.3),也没有虚函数类(10.1)和

- 选择复制/移动每个直接基类子对象的构造函数是微不足道的,而且

- 对于类型为(或其数组)的X的每个非静态数据成员,选择复制/移动该成员的构造函数是微不足道的;

否则复制/移动构造函数是不平凡的。

基本上这意味着如果复制或移动构造函数不是用户提供的,则该类没有任何虚拟内容,并且此属性对于类和基类的所有成员都递归地保存。

简单复制/移动赋值运算符的定义非常相似,只需将“构造函数”替换为“赋值运算符”即可。

一个微不足道的析构函数也有类似的定义,增加了一个约束,它不能是虚拟的。

而另一个类似的规则存在于简单的默认构造函数中,另外,如果类有非静态数据成员,并带有括号或等于初始值设定项,那么默认的构造函数不是微不足道的。

以下是一些清除所有内容的示例:

// empty classes are trivial
struct Trivial1 {};

// all special members are implicit
struct Trivial2 {
    int x;
};

struct Trivial3 : Trivial2 { // base class is trivial
    Trivial3() = default; // not a user-provided ctor
    int y;
};

struct Trivial4 {
public:
    int a;
private: // no restrictions on access modifiers
    int b;
};

struct Trivial5 {
    Trivial1 a;
    Trivial2 b;
    Trivial3 c;
    Trivial4 d;
};

struct Trivial6 {
    Trivial2 a[23];
};

struct Trivial7 {
    Trivial6 c;
    void f(); // it's okay to have non-virtual functions
};

struct Trivial8 {
     int x;
     static NonTrivial1 y; // no restrictions on static members
};

struct Trivial9 {
     Trivial9() = default; // not user-provided
      // a regular constructor is okay because we still have default ctor
     Trivial9(int x) : x(x) {};
     int x;
};

struct NonTrivial1 : Trivial3 {
    virtual void f(); // virtual members make non-trivial ctors
};

struct NonTrivial2 {
    NonTrivial2() : z(42) {} // user-provided ctor
    int z;
};

struct NonTrivial3 {
    NonTrivial3(); // user-provided ctor
    int w;
};
NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
                                      // still counts as user-provided
struct NonTrivial5 {
    virtual ~NonTrivial5(); // virtual destructors are not trivial
};

标准布局

标准布局是第二个属性。 该标准提到这些对于与其他语言进行通信很有用,这是因为标准布局类具有相同的等效C结构或联合的内存布局。

这是另一个必须递归保存成员和所有基类的属性。 像往常一样,不允许虚拟函数或虚拟基类。 这会使布局与C不兼容。

这里宽松的规则是标准布局类必须具有所有具有相同访问控制的非静态数据成员。 以前这些都必须是全部公开的 ,但现在只要它们全部是私人的或全部受保护的,现在你可以将它们变为私有的或受保护的。

在使用继承时,整个继承树中只有一个类可以有非静态数据成员,并且第一个非静态数据成员不能是基类类型(这可能会打破别名规则),否则,它不是标准类,布局类。

这就是标准文本中的定义:

标准布局类是一个类:

- 没有类型非标准布局类(或这种类型的数组)的非静态数据成员或引用,

- 没有虚函数(10.3),没有虚拟基类(10.1),

- 对所有非静态数据成员具有相同的访问控制(第11章)

- 没有非标准布局的基类,

- 在大多数派生类中最多有一个非静态数据成员,最多有一个基类具有非静态数据成员,或者没有包含非静态数据成员的基类,以及

- 没有与第一个非静态数据成员相同类型的基类。

标准布局结构是使用类关键字结构或类关键字类定义的标准布局类。

标准布局联合是使用类键联合定义的标准布局类。

[ 注意:标准布局类对于使用其他编程语言编写的代码进行通信很有用。 它们的布局在9.2中指定。 - 注意 ]

我们来看几个例子。

// empty classes have standard-layout
struct StandardLayout1 {};

struct StandardLayout2 {
    int x;
};

struct StandardLayout3 {
private: // both are private, so it's ok
    int x;
    int y;
};

struct StandardLayout4 : StandardLayout1 {
    int x;
    int y;

    void f(); // perfectly fine to have non-virtual functions
};

struct StandardLayout5 : StandardLayout1 {
    int x;
    StandardLayout1 y; // can have members of base type if they're not the first
};

struct StandardLayout6 : StandardLayout1, StandardLayout5 {
    // can use multiple inheritance as long only
    // one class in the hierarchy has non-static data members
};

struct StandardLayout7 {
    int x;
    int y;
    StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};

struct StandardLayout8 {
public:
    StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
    int x;
};

struct StandardLayout9 {
    int x;
    static NonStandardLayout1 y; // no restrictions on static members
};

struct NonStandardLayout1 {
    virtual f(); // cannot have virtual functions
};

struct NonStandardLayout2 {
    NonStandardLayout1 X; // has non-standard-layout member
};

struct NonStandardLayout3 : StandardLayout1 {
    StandardLayout1 x; // first member cannot be of the same type as base
};

struct NonStandardLayout4 : StandardLayout3 {
    int z; // more than one class has non-static data members
};

struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class

结论

有了这些新规则,现在可以有更多类型的POD。 即使一个类型不是POD,我们也可以分别利用一些POD属性(如果它只是一种普通或标准布局)。

标准库具有在头文件<type_traits>测试这些属性的特性:

template <typename T>
struct std::is_pod;
template <typename T>
struct std::is_trivial;
template <typename T>
struct std::is_trivially_copyable;
template <typename T>
struct std::is_standard_layout;

Cpp参考总是很有用!!! 有关显式说明符的详细信息,请参见here 。 您可能还需要查看隐式转换copy-initialization

快速浏览

显式说明符指定构造函数或转换函数(自C ++ 11以来)不允许隐式转换或复制初始化。

示例如下:

struct A
{
    A(int) { }      // converting constructor
    A(int, int) { } // converting constructor (C++11)
    operator bool() const { return true; }
};

struct B
{
    explicit B(int) { }
    explicit B(int, int) { }
    explicit operator bool() const { return true; }
};

int main()
{
    A a1 = 1;      // OK: copy-initialization selects A::A(int)
    A a2(2);       // OK: direct-initialization selects A::A(int)
    A a3 {4, 5};   // OK: direct-list-initialization selects A::A(int, int)
    A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
    A a5 = (A)1;   // OK: explicit cast performs static_cast
    if (a1) cout << "true" << endl; // OK: A::operator bool()
    bool na1 = a1; // OK: copy-initialization selects A::operator bool()
    bool na2 = static_cast<bool>(a1); // OK: static_cast performs direct-initialization

//  B b1 = 1;      // error: copy-initialization does not consider B::B(int)
    B b2(2);       // OK: direct-initialization selects B::B(int)
    B b3 {4, 5};   // OK: direct-list-initialization selects B::B(int, int)
//  B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
    B b5 = (B)1;   // OK: explicit cast performs static_cast
    if (b5) cout << "true" << endl; // OK: B::operator bool()
//  bool nb1 = b2; // error: copy-initialization does not consider B::operator bool()
    bool nb2 = static_cast<bool>(b2); // OK: static_cast performs direct-initialization
}




c++ aggregate c++-faq pod c++11