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

What are Aggregates and PODs and how/why are they special?

本常见问题解答涉及集料和豆荚,并涵盖以下材料:

  • 什么是聚合?
  • 什么是pods(普通的旧数据)?
  • 它们是如何联系的?
  • 它们是如何和为什么特别的?
  • C++ 11有什么变化?


如何阅读:

这篇文章相当长。如果您想了解聚合和pods(普通的旧数据),请花点时间阅读它。如果您只对聚合感兴趣,请阅读第一部分。如果您只对pods感兴趣,那么您必须首先阅读聚合的定义、含义和示例,然后您可以跳到pods,但我仍然建议您完整阅读第一部分。聚合的概念对于定义pods是必不可少的。如果您发现任何错误(即使是很小的错误,包括语法、文体学、格式、语法等),请留言,我将进行编辑。好的。

这个答案适用于C++ 03。对于其他C++标准,请参见:好的。

  • C++ 11的变化
  • C++ 14的变化
  • C++ 17的变化

什么是聚合体,为什么它们是特殊的

C++标准的形式化定义(C++ 03 8.5.1节1):好的。

An aggregate is an array or a class (clause 9) with no user-declared
constructors (12.1), no private or protected non-static data members (clause 11),
no base classes (clause 10), and no virtual functions (10.3).

Ok.

好吧,让我们分析一下这个定义。首先,任何数组都是聚合。类也可以是聚合,如果…等等!关于结构或联合,什么都没有说,它们不能是聚合的吗?是的,他们能。在C++中,术语EDCOX1〔0〕指的是所有类、结构和联合。因此,如果类(或结构或联合)满足上述定义中的条件,则它是一个聚合。这些标准意味着什么?好的。

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

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

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

  • 数组是聚合,即使它是非聚合类类型的数组。好的。

现在让我们来看一些例子:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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] = {a1, a2, …, am};好的。

如果(m==n)数组的第i个元素用ai初始化否则(m<n)数组的前m个元素初始化为a1、a2、…、am和其他n - m元素,如果可能,值初始化(请参见下文了解术语的解释)否则(m> n)编译器将发出一个错误否则(如int a[] = {1, 2, 3};中完全没有指定n的情况就是这种情况)假设数组(n)的大小等于m,所以int a[] = {1, 2, 3};等于int a[3] = {1, 2, 3};。好的。

当一个标量类型的对象(boolintchardouble、指针等)被值初始化时,表示该对象被该类型的0初始化(false代表bool0.0代表double等)。当具有用户声明的默认构造函数的类类型的对象为值初始化时,将调用其默认构造函数。如果默认构造函数是隐式定义的,那么所有非静态成员都是递归值初始化的。这个定义不精确,有点不正确,但它应该给你一个基本的概念。引用不能初始化值。例如,如果类没有适当的默认构造函数,则非聚合类的值初始化可能会失败。好的。

数组初始化示例:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
}

现在让我们看看如何用大括号初始化聚合类。差不多一样。代替数组元素,我们将按照非静态数据成员在类定义中的出现顺序来初始化它们(它们都是按定义公开的)。如果初始值设定项少于成员,则其余的初始化为值。如果无法对其中一个未显式初始化的成员进行值初始化,则会得到一个编译时错误。如果初始值设定项超过了需要的数量,我们也会得到一个编译时错误。好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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}};

在上例中,y.c'a'初始化,y.x.i110初始化,y.x.i220初始化,y.i[0]20初始化,y.i[1]30初始化,y.f0.0初始化。受保护的静态成员d根本没有初始化,因为它是static。好的。

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

既然我们知道了聚合的特殊之处,那么我们就试着理解对类的限制;也就是说,它们为什么存在。我们应该理解,带大括号的memberwise初始化意味着类只不过是其成员的总和。如果存在用户定义的构造函数,这意味着用户需要做一些额外的工作来初始化成员,因此大括号初始化将不正确。如果存在虚函数,这意味着这个类的对象(在大多数实现中)有一个指向类的所谓vtable的指针,而vtable是在构造函数中设置的,因此大括号初始化是不够的。您可以用与练习类似的方式来计算其余的限制条件:)。好的。

关于聚集体就足够了。现在我们可以定义一组更严格的类型,也就是说,pods好的。豆荚是什么?为什么它们很特别?

C++标准的形式化定义(C++ 03 9节4):好的。

A POD-struct is an aggregate class
that has no non-static data members of
type non-POD-struct, non-POD-union (or
array of such types) or reference, and
has no user-defined copy assignment
operator and no user-defined
destructor. Similarly, a POD-union is
an aggregate union that has no
non-static data members of type
non-POD-struct, non-POD-union (or
array of such types) or reference, and
has no user-defined copy assignment
operator and no user-defined
destructor. A POD class is a class
that is either a POD-struct or a
POD-union.

Ok.

哇,这个更难分析,不是吗?:)让我们将工会排除在外(基于与上述相同的理由),并以更清晰的方式重新表述:好的。

An aggregate class is called a POD if
it has no user-defined copy-assignment
operator and destructor and none of
its nonstatic members is a non-POD
class, array of non-POD, or a
reference.

Ok.

这个定义意味着什么?(我有没有提到pod代表简单的旧数据?)好的。

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

实例:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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类最接近C结构。与之不同的是,pods可以有成员函数和任意静态成员,但这两者都不能改变对象的内存布局。因此,如果您想编写一个或多或少可以从C甚至.NET使用的可移植动态库,您应该尝试使所有导出的函数只接受和返回pod类型的参数。好的。

  • 非pod类类型的对象的生存期从构造函数完成时开始,到析构函数完成时结束。对于pod类,生存期从对象的存储被占用时开始,到释放或重用该存储时结束。好的。

  • 对于pod类型的对象,标准保证当您将对象的内容放入char或unsigned char数组中,然后将内容返回到对象中时,对象将保留其原始值。请注意,对于非POD类型的对象没有这样的保证。此外,您还可以使用memcpy安全地复制pod对象。以下示例假设t是pod类型:好的。

    1
    2
    3
    4
    5
    6
    7
    #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从某个变量还不在作用域内的点跳转到已经在作用域内的点是非法的(编译器应该发出错误)。只有当变量为非POD类型时,此限制才适用。在下面的示例中,f()的格式不正确,而g()的格式正确。注意,微软的编译器对于这条规则过于开放,它在这两种情况下都会发出警告。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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类a的第一个成员是t类型,则可以安全地从A*T*得到指向第一个成员的指针,反之亦然。好的。

名单上有很多…好的。结论

理解pod到底是什么是很重要的,因为正如您所看到的,许多语言特性对它们的行为都是不同的。好的。好啊。


C++ 11有什么变化?聚集体

集料的标准定义略有变化,但仍大致相同:好的。

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1),
no brace-or-equal-initializers for non-static data members (9.2), no private or protected
non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

Ok.

好吧,发生了什么变化?好的。

  • 以前,聚合不能有用户声明的构造函数,但现在不能有用户提供的构造函数。有什么区别吗?是的,有,因为现在您可以声明构造函数并默认它们:好的。

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

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

  • 现在,对于非静态数据成员,聚合不能有任何大括号或相等的初始值设定项。这是什么意思?好吧,这只是因为使用这个新标准,我们可以直接在类中初始化成员,如下所示:好的。

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

    使用这个特性使类不再是聚合类,因为它基本上等同于提供您自己的默认构造函数。好的。

  • 所以,总的来说变化不大。它仍然是相同的基本思想,适应新特性。好的。豆荚呢?

    豆荚经历了很多变化。在这个新标准中,以前关于pods的很多规则都被放宽了,标准中定义的方式也发生了根本性的变化。好的。

    POD的概念是捕捉两种不同的特性:好的。

  • 它支持静态初始化,并且
  • 在C++中编译一个POD给您与在C.编译的结构相同的内存布局。
  • 因此,定义被分为两个不同的概念:普通类和标准布局类,因为它们比POD更有用。标准现在很少使用pod这个术语,更喜欢更具体的琐碎和标准的布局概念。好的。

    新定义基本上表示pod是一个既普通又具有标准布局的类,并且对于所有非静态数据成员,此属性必须递归地保持:好的。

    A POD struct is a non-union class that is both a trivial class and a standard-layout class,
    and has no non-static data members of type non-POD struct, non-POD union (or array of such types).
    Similarly, a POD union is a union that is both a trivial class and a standard layout class, and has
    no non-static data members of type non-POD struct, non-POD union (or array of such types).
    A POD class is a class that is either a POD struct or a POD union.

    Ok.

    让我们分别详细介绍这两个属性中的每一个。好的。型普通类

    Trivial是上面提到的第一个属性:Trivial类支持静态初始化。如果一个类是可复制的(一个小类的超集),那么可以用memcpy之类的东西复制它的表示,并期望结果是相同的。好的。型

    该标准定义了一个普通类,如下所示:好的。型

    A trivially copyable class is a class that:

    Ok.

    — has no non-trivial copy constructors (12.8),

    Ok.

    — has no non-trivial move constructors (12.8),

    Ok.

    — has no non-trivial copy assignment operators (13.5.3, 12.8),

    Ok.

    — has no non-trivial move assignment operators (13.5.3, 12.8), and

    Ok.

    — has a trivial destructor (12.4).

    Ok.

    A trivial class is a class that has a trivial default constructor (12.1) and is trivially copyable.

    Ok.

    [ Note: In particular, a trivially copyable or trivial class does not have virtual functions
    or virtual base classes.—end note ]

    Ok.

    那么,那些琐碎的和非琐碎的东西是什么呢?好的。型

    A copy/move constructor for class X is trivial if it is not user-provided and if

    Ok.

    — class X has no virtual functions (10.3) and no virtual base classes (10.1), and

    Ok.

    — the constructor selected to copy/move each direct base class subobject is trivial, and

    Ok.

    — for each non-static data member of X that is of class type (or array thereof), the constructor
    selected to copy/move that member is trivial;

    Ok.

    otherwise the copy/move constructor is non-trivial.

    Ok.

    基本上,这意味着复制或移动构造函数是很普通的,如果不是用户提供的,类中没有虚拟的内容,并且这个属性对于类的所有成员和基类都是递归的。好的。型

    简单的复制/移动分配操作符的定义非常相似,只需将"constructor"一词替换为"assignment operator"。好的。型

    一个普通的析构函数也有一个类似的定义,附加的约束是它不能是虚拟的。好的。型

    对于普通的默认构造函数,还有另一个类似的规则存在,另外一个规则是,如果类具有带大括号或相等初始值设定项的非静态数据成员,则默认构造函数并不简单,我们在上面已经看到了这一点。好的。型

    下面是一些清除所有内容的示例:好的。型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    // 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不兼容。好的。

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

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

    标准文本中的定义如下:好的。

    A standard-layout class is a class that:

    Ok.

    — has no non-static data members of type non-standard-layout class (or array of such types)
    or reference,

    Ok.

    — has no virtual functions (10.3) and no virtual base classes (10.1),

    Ok.

    — has the same access control (Clause 11) for all non-static data members,

    Ok.

    — has no non-standard-layout base classes,

    Ok.

    — either has no non-static data members in the most derived class and at most one base class with
    non-static data members, or has no base classes with non-static data members, and

    Ok.

    — has no base classes of the same type as the first non-static data member.

    Ok.

    A standard-layout struct is a standard-layout class defined with the class-key struct or
    the class-key class.

    Ok.

    A standard-layout union is a standard-layout class defined with the class-key union.

    Ok.

    [ Note: Standard-layout classes are useful for communicating with code written in other programming languages. Their layout is specified in 9.2.—end note ]

    Ok.

    我们来看几个例子。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    // 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

    结论

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

    标准库具有在标题中测试这些属性的特性:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    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;

    好啊。


    C++ 14发生了什么变化

    我们可以参看C++ 14标准草案以供参考。

    聚集体

    这一点在8.5.1部分中有介绍,给出了以下定义:

    An aggregate is an array or a class (Clause 9) with no user-provided
    constructors (12.1), no private or protected non-static data members
    (Clause 11), no base classes (Clause 10), and no virtual functions
    (10.3).

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

    1
    2
    3
    4
    5
    struct A
    {
      int a = 3;
      int b = 3;
    };

    不是C++ 11中的集合,而是C++ 14中的集合。此更改包含在N3605:成员初始值设定项和聚合中,其摘要如下:

    Bjarne Stroustrup and Richard Smith raised an issue about aggregate
    initialization and member-initializers not working together. This
    paper proposes to fix the issue by adopting Smith's proposed wording
    that removes a restriction that aggregates can't have
    member-initializers.

    POD保持不变

    pod(普通旧数据)结构的定义在9类中介绍,该类表示:

    A POD struct110 is a non-union class that is both a trivial class and
    a standard-layout class, and has no non-static data members of type
    non-POD struct, non-POD union (or array of such types). Similarly, a
    POD union is a union that is both a trivial class and a
    standard-layout class, and has no non-static data members of type
    non-POD struct, non-POD union (or array of such types). A POD class is
    a class that is either a POD struct or a POD union.

    这是和C++ 11相同的措辞。

    C++ 14的标准布局更改

    如注释中所提到的,POD依赖于标准布局的定义,并且对于C++ 14确实发生了变化,但这是通过在事实之后应用到C++ 14的缺陷报告。

    共有三个DRS:

    • DR 1672
    • DR 1813
    • DR 2120

    所以标准布局从这个前C + + 14开始:

    A standard-layout class is a class that:

    • (7.1) has no non-static data members of type non-standard-layout class (or array of such types) or reference,
    • (7.2) has no virtual functions ([class.virtual]) and no virtual base classes ([class.mi]),
    • (7.3) has the same access control (Clause [class.access]) for all non-static data members,
    • (7.4) has no non-standard-layout base classes,
    • (7.5) either has no non-static data members in the most derived class and at most one base class with non-static data members, or has
      no base classes with non-static data members, and
    • (7.6) has no base classes of the same type as the first non-static data member.109

    在C++ 14中:

    A class S is a standard-layout class if it:

    • (3.1) has no non-static data members of type non-standard-layout class (or array of such types) or reference,
    • (3.2) has no virtual functions and no virtual base classes,
    • (3.3) has the same access control for all non-static data members,
    • (3.4) has no non-standard-layout base classes,
    • (3.5) has at most one base class subobject of any given type,
    • (3.6) has all non-static data members and bit-fields in the class and its base classes first declared in the same class, and
    • (3.7) has no element of the set M(S) of types as a base class, where for any type X, M(X) is defined as follows.104
      [?Note: M(X) is the set of the types of all non-base-class subobjects that may be at a zero offset in X.
      —?end note
      ?]

      • (3.7.1) If X is a non-union class type with no (possibly inherited) non-static data members, the set M(X) is empty.
      • (3.7.2) If X is a non-union class type with a non-static data member of type X0 that is either of zero size or is the first
        non-static data member of X (where said member may be an anonymous
        union), the set M(X) consists of X0 and the elements of M(X0).
      • (3.7.3) If X is a union type, the set M(X) is the union of all M(Ui) and the set containing all Ui, where each Ui is the type of the
        ith non-static data member of X.
      • (3.7.4) If X is an array type with element type Xe, the set M(X) consists of Xe and the elements of M(Xe).
      • (3.7.5) If X is a non-class, non-array type, the set M(X) is empty.


    can you please elaborate following rules:

    我会尝试:

    a) standard-layout classes must have all non-static data members with the same access control

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

    对它们的推理是为了区分"标准布局"和"非标准布局"。也就是说,赋予编译器选择如何将事物放入内存的自由。不仅仅是关于vtable指针。

    当他们在98标准化C++时,他们必须基本上预测人们是如何实现它的。虽然他们有很多不同的C++语言的执行经验,但他们对事情并不确定。所以他们决定要谨慎:给编者尽可能多的自由。

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

    当C++ 11被处理时,他们对编译器有更多的经验。他们意识到…C++编译器编写者真的很懒。他们有所有的自由,但他们没有做任何事情。

    标准布局的规则或多或少是编纂惯例:大多数编译器根本不需要改变太多(如果有任何改变的话)来实现它们(对于相应的类型特征,可能不需要一些东西)。

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

    事实上,它并没有真正伤害到用户。如果您正在创建一个封装类,那么您的所有数据成员都很可能是private。通常不会在完全封装的类型上公开公共数据成员。所以这对于少数想要这样做的用户来说只是个问题,他们想要这个部门。

    所以损失不大。

    b) only one class in the whole inheritance tree can have non-static data members,

    这一点的原因可以追溯到为什么他们再次标准化了标准布局:常见的实践。

    当一个继承树中有两个成员实际存储东西时,没有一个常见的实践。有些将基类放在派生类之前,另一些则相反。如果成员来自两个基本类,您如何订购它们?等等。编者在这些问题上意见分歧很大。

    另外,由于零/一/无限规则,一旦你说你可以有两个带成员的类,你可以说任意多的。这需要添加很多布局规则来处理这个问题。您必须说明多重继承是如何工作的,哪些类把它们的数据放在其他类之前,等等。这是很多规则,只需要很少的物质收益。

    不能使没有虚拟函数和默认构造函数标准布局的所有内容都成为可能。

    and the first non-static data member cannot be of a base class type (this could break aliasing rules).

    我真的不能和这个人说话。在C++的别名规则中,我还没有足够的知识来真正理解它。但它与这样一个事实有关:基础成员将与基础类本身共享相同的地址。即:

    1
    2
    3
    4
    5
    struct Base {};
    struct Derived : Base { Base b; };

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

    这可能是针对C++的别名规则。在某种程度上。

    然而,考虑一下这个问题:拥有这样的能力到底有多大用处?因为只有一个类可以有非静态数据成员,所以Derived必须是该类(因为它有一个Base作为成员)。因此,Base必须为空(属于数据)。如果Base是空的,以及一个基类…为什么它有一个数据成员?

    由于Base是空的,所以没有状态。因此,任何非静态成员函数都将根据它们的参数而不是它们的this指针来执行它们的操作。

    所以再次重申:没有大损失。


    C++ 17的更改

    在这里下载C++ 17国际标准最终草案。好的。

    聚集体好的。

    C++ 17扩展和增强聚集和聚合初始化。标准库现在还包括一个std::is_aggregate类型的特征类。以下是第11.6.1.1和11.6.1.2节中的正式定义(删除内部参考文献):好的。

    An aggregate is an array or a class with
    — no user-provided, explicit, or inherited constructors,
    — no private or protected non-static data members,
    — no virtual functions, and
    — no virtual, private, or protected base classes.
    [ Note: Aggregate initialization does not allow accessing protected and private base class’ members or constructors. —end note ]
    The elements of an aggregate are:
    — for an array, the array elements in increasing subscript order, or
    — for a class, the direct base classes in declaration order, followed by the direct non-static data members that are not members of an anonymous union, in declaration order.

    Ok.

    什么改变了?好的。

  • 聚合现在可以有公共的、非虚拟的基类。此外,基本类不是聚合的要求。如果它们不是聚合的,那么它们是列表初始化的。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    struct B1 // not a aggregate
    {
        int i1;
        B1(int a) : i1(a) { }
    };
    struct B2
    {
        int i2;
        B2() = default;
    };
    struct M // not an aggregate
    {
        int m;
        M(int a) : m(a) { }
    };
    struct C : B1, B2
    {
        int j;
        M m;
        C() = default;
    };
    C c { { 1 }, { 2 }, 3, { 4 } };
    cout
        &lt&lt"is C aggregate?:" &lt&lt (std::is_aggregate::value ? 'Y' : 'N')
        &lt&lt" i1:" &lt&lt c.i1 &lt&lt" i2:" &lt&lt c.i2
        &lt&lt" j:" &lt&lt c.j &lt&lt" m.m:" &lt&lt c.m.m &lt&lt endl;

    //stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  • 不允许显式默认构造函数
  • 1
    2
    3
    4
    5
    6
    struct D // not an aggregate
    {
        int i = 0;
        D() = default;
        explicit D(D const&) = default;
    };
  • 不允许继承构造函数
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    struct B1
    {
        int i1;
        B1() : i1(0) { }
    };
    struct C : B1 // not an aggregate
    {
        using B1::B1;
    };

    平凡类好的。

    在C++ 17中对琐碎类的定义进行了修改,以解决C++ 14中未解决的几个缺陷。这些变化本质上是技术性的。以下是12.0.6中的新定义(删除了内部引用):好的。

    A trivially copyable class is a class:
    — where each copy constructor, move constructor, copy assignment operator, and move assignment operator is either deleted or trivial,
    — that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and
    — that has a trivial, non-deleted destructor.
    A trivial class is a class that is trivially copyable and has one or more default constructors, all of which are either trivial or deleted and at least one of which is not deleted. [ Note: In particular, a trivially copyable
    or trivial class does not have virtual functions or virtual base classes.—end note ]

    Ok.

    变化:好的。

  • 在C++ 14下,对于一个微不足道的类,类不能有任何非平凡的复制/移动构造函数/赋值操作符。但是,隐式声明为默认的构造函数/运算符可能是非常重要的,但也可能被定义为已删除,因为例如,类包含无法复制/移动的类类型的子对象。这种非平凡的(定义为已删除的构造函数/运算符)的存在将导致整个类非平凡。析构函数也存在类似的问题。C++ 17阐明了这样的构造函数/运算符的存在不会导致类是非平凡的可复制的,因此非平凡的,并且平凡的可复制类必须有一个平凡的、未删除的析构函数。DR1734,DR1928
  • C++ 14允许一个平凡的可复制类,因此是一个平凡的类,使每个复制/移动构造函数/赋值操作符声明为已删除。但是,如果类也是标准布局,则可以与std::memcpy一起合法复制/移动。这是一个语义上的矛盾,因为通过将类的创建者定义为已删除的所有构造函数/赋值运算符,显然是希望类不能被复制/移动,但类仍然满足可复制类的定义。因此,在C++ 17中,我们有一个新的子句说明,平凡的可复制类必须至少有一个平凡的、未删除的(虽然不一定是公共可访问的)复制/移动构造函数/赋值操作符。见N4148、DR1734
  • 第三个技术更改涉及默认构造函数的类似问题。在C++ 14中,一个类可以有隐含的默认构造函数,这些构造函数隐含地定义为删除,但仍然是一个平凡的类。新定义澄清了一个普通类必须至少有一个普通的、未删除的默认构造函数。参见DR1496
  • 标准布局类好的。

    对标准布局的定义也进行了修改,以解决缺陷报告。这些变化同样是技术性的。以下是标准(12.0.7)的文本。如前所述,删除内部引用:好的。

    A class S is a standard-layout class if it:
    — has no non-static data members of type non-standard-layout class (or array of such types) or reference,
    — has no virtual functions and no virtual base classes,
    — has the same access control for all non-static data members,
    — has no non-standard-layout base classes,
    — has at most one base class subobject of any given type,
    — has all non-static data members and bit-fields in the class and its base classes first declared in the same class, and
    — has no element of the set M(S) of types (defined below) as a base class.108
    M(X) is defined as follows:
    — If X is a non-union class type with no (possibly inherited) non-static data members, the set M(X) is empty.
    — If X is a non-union class type whose first non-static data member has type X0 (where said member may be an anonymous union), the set M(X) consists of X0 and the elements of M(X0).
    — If X is a union type, the set M(X) is the union of all M(Ui) and the set containing all Ui, where each Ui is the type of the ith non-static data member of X.
    — If X is an array type with element type Xe, the set M(X) consists of Xe and the elements of M(Xe).
    — If X is a non-class, non-array type, the set M(X) is empty.
    [ Note: M(X) is the set of the types of all non-base-class subobjects that are guaranteed in a standard-layout class to be at a zero offset in X. —end note ]
    [ Example:

    1
    2
    3
    4
    5
    6
    7
    8
    struct B { int i; }; // standard-layout class
    struct C : B { }; // standard-layout class
    struct D : C { }; // standard-layout class
    struct E : D { char : 4; }; // not a standard-layout class
    struct Q {};
    struct S : Q { };
    struct T : Q { };
    struct U : S, T { }; // not a standard-layout class

    —end example ]
    108) This ensures that two subobjects that have the same class type and that belong to the same most derived object are not allocated at the same address.

    Ok.

    变化:好的。

  • 阐明了派生树中只有一个类"具有"非静态数据成员的要求是指首先声明此类数据成员的类,而不是可能继承这些数据成员的类,并将此要求扩展到非静态位字段。还澄清了标准布局类"最多有一个给定类型的基类子对象。"参见DR1813、DR1881
  • 标准布局的定义从未允许任何基类的类型与第一个非静态数据成员的类型相同。避免偏移量为零的数据成员与任何基类具有相同类型的情况。C++ 17标准提供了一个更严格、递归的定义:"在标准布局类中保证的所有非基类子对象类型的集合为01:00偏移",从而禁止此类类型成为任何基类的类型。见DR1672、DR2120。
  • 注意:C++标准委员会希望基于缺陷报告的上述更改适用于C++ 14,尽管新语言不在已发布的C++ 14标准中。它是在C++ 17标准中的。好的。好啊。


    C++ 20会发生什么变化

    这还为时过早,因此有些答案将来可能会改变。遵循这个问题的其他明确主题,聚合的含义和使用继续随着每个标准的变化而变化。目前有几个关键的变化。

    具有用户声明的构造函数的类型P1008

    在C++ 17中,这种类型仍然是一个集合:

    1
    2
    3
    struct X {
        X() = delete;
    };

    因此,X{}仍然编译,因为这是聚合初始化,而不是构造函数调用。另请参见:什么时候私有构造函数不是私有构造函数?

    在C++ 20中,限制将根据需求而改变:

    no user-provided, explicit, or inherited constructors

    no user-declared or inherited constructors

    这已经被应用到C++ 20的工作草案中。在链接问题中,EDCOX1的"3"和EDCOX1(4)都不会在C++ 20中聚集。

    从带括号的值列表p960初始化聚合

    出现的一个常见问题是希望使用带有聚合的emplace()样式的构造函数:

    1
    2
    3
    struct X { int a, b; };
    std::vector<X> xs;
    xs.emplace_back(1, 2); // error

    这不起作用,因为emplace将尝试有效地执行无效的初始化X(1, 2)。典型的解决方案是在X中添加一个构造函数,但是有了这个建议(目前正在通过核心工作),聚合将有效地合成了执行正确操作的构造函数,并且表现得像常规的构造函数。上面的代码将像C++ 20那样编译(假设这个特性得到批准,这似乎是可能的)。

    聚合p1021的类模板参数推导(ctad)

    在C++ 17中,这不编译:

    1
    2
    3
    4
    5
    6
    template <typename T>
    struct Point {
        T x, y;
    };

    Point p{1, 2}; // error

    用户必须为所有聚合模板编写自己的扣减指南:

    1
    template <typename T> Point(T, T) -> Point<T>;

    但由于这在某种意义上是"显而易见的事情",而且基本上只是样板文件,所以语言将为您做到这一点。这种变化在2018年11月得到了进化的认可,所以上面的例子很可能在C++ 20中编译(不需要用户提供的演绎指南)。