C++ 什么是聚合和 POD 以及它们如何/为什么特别?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4178175/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 14:45:31  来源:igfitidea点击:

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

c++c++11aggregatec++17standard-layout

提问by Armen Tsirunyan

This FAQis about Aggregates and PODs and covers the following material:

常见问题解答是关于聚合和 POD 的,涵盖以下材料:

  • What are Aggregates?
  • What are PODs (Plain Old Data)?
  • How are they related?
  • How and why are they special?
  • What changes for C++11?
  • 什么是聚合
  • 什么是POD(普通旧数据)?
  • 它们有什么关系?
  • 它们如何以及为什么特别?
  • C++11 有哪些变化?

回答by Armen Tsirunyan

How to read:

如何阅读:

This article is rather long. If you want to know about both aggregates and PODs (Plain Old Data) take time and read it. If you are interested just in aggregates, read only the first part. If you are interested only in PODs then you must first read the definition, implications, and examples of aggregates and then you mayjump to PODs but I would still recommend reading the first part in its entirety. The notion of aggregates is essential for defining PODs. If you find any errors (even minor, including grammar, stylistics, formatting, syntax, etc.) please leave a comment, I'll edit.

这篇文章比较长。如果您想了解聚合和 POD(普通旧数据),请花点时间阅读它。如果您只对聚合感兴趣,请阅读第一部分。如果您只对 POD 感兴趣,那么您必须首先阅读聚合的定义、含义和示例,然后您可以跳转到 POD,但我仍然建议您完整阅读第一部分。聚合的概念对于定义 POD 至关重要。如果您发现任何错误(即使是轻微的,包括语法、文体、格式、语法等),请发表评论,我会进行编辑。

This answer applies to C++03. For other C++ standards see:

此答案适用于 C++03。对于其他 C++ 标准,请参见:

What are aggregates and why they are special

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

Formal definition from the C++ standard (C++03 8.5.1 §1):

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).

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

So, OK, let's parse this definition. First of all, any array is an aggregate. A class can also be an aggregate if… wait! nothing is said about structs or unions, can't they be aggregates? Yes, they can. In C++, the term classrefers to all classes, structs, and unions. So, a class (or struct, or union) is an aggregate if and only if it satisfies the criteria from the above definitions. What do these criteria imply?

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

  • This does not mean an aggregate class cannot have constructors, in fact it can have a default constructor and/or a copy constructor as long as they are implicitly declared by the compiler, and not explicitly by the user

  • No private or protected non-static data members. You can have as many private and protected member functions (but not constructors) as well as as many private or protected staticdata members and member functions as you like and not violate the rules for aggregate classes

  • An aggregate class can have a user-declared/user-defined copy-assignment operator and/or destructor

  • An array is an aggregate even if it is an array of non-aggregate class type.

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

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

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

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

Now let's look at some examples:

现在让我们看一些例子:

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
};

You get the idea. Now let's see how aggregates are special. They, unlike non-aggregate classes, can be initialized with curly braces {}. This initialization syntax is commonly known for arrays, and we just learnt that these are aggregates. So, let's start with them.

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

Type array_name[n] = {a1, a2, …, am};

Type array_name[n] = {a1, a2, …, am};

if(m == n)
the ithelement of the array is initialized with ai
else if(m < n)
the first m elements of the array are initialized with a1, a2, …, amand the other n - melements are, if possible, value-initialized(see below for the explanation of the term)
else if(m > n)
the compiler will issue an error
else(this is the case when n isn't specified at all like int a[] = {1, 2, 3};)
the size of the array (n) is assumed to be equal to m, so int a[] = {1, 2, 3};is equivalent to int a[3] = {1, 2, 3};

if(m == n)数组的
i元素用 ai初始化
else if(m < n)
数组的前 m 个元素用 a1, a2, ..., am和其他n - m元素初始化是,如果可能,值初始化(见下文对术语的解释)
else if(m > n)
编译器将发出一个错误
else(当 n 完全没有被指定时就是这种情况int a[] = {1, 2, 3};
的大小假设数组 (n) 等于 m,因此int a[] = {1, 2, 3};等价于int a[3] = {1, 2, 3};

When an object of scalar type (bool, int, char, double, pointers, etc.) is value-initializedit means it is initialized with 0for that type (falsefor bool, 0.0for double, etc.). When an object of class type with a user-declared default constructor is value-initialized its default constructor is called. If the default constructor is implicitly defined then all nonstatic members are recursively value-initialized. This definition is imprecise and a bit incorrect but it should give you the basic idea. A reference cannot be value-initialized. Value-initialization for a non-aggregate class can fail if, for example, the class has no appropriate default constructor.

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

Examples of array initialization:

数组初始化示例:

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
}

Now let's see how aggregate classes can be initialized with braces. Pretty much the same way. Instead of the array elements we will initialize the non-static data members in the order of their appearance in the class definition (they are all public by definition). If there are fewer initializers than members, the rest are value-initialized. If it is impossible to value-initialize one of the members which were not explicitly initialized, we get a compile-time error. If there are more initializers than necessary, we get a compile-time error as well.

现在让我们看看如何使用花括号初始化聚合类。几乎相同的方式。我们将按照在类定义中出现的顺序初始化非静态数据成员,而不是数组元素(根据定义,它们都是公共的)。如果初始化器比成员少,则其余的都是值初始化的。如果无法对未显式初始化的成员之一进行值初始化,则会出现编译时错误。如果初始值设定项过多,我们也会收到编译时错误。

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}};

In the above example y.cis initialized with 'a', y.x.i1with 10, y.x.i2with 20, y.i[0]with 20, y.i[1]with 30and y.fis value-initialized, that is, initialized with 0.0. The protected static member dis not initialized at all, because it is static.

上面的例子中y.c是用'a', y.x.i1with 10, y.x.i2with 20, y.i[0]with 20, y.i[1]with30y.f进行了值初始化,即用 初始化0.0。受保护的静态成员d根本没有初始化,因为它是static.

Aggregate unions are different in that you may initialize only their first member with braces. I think that if you are advanced enough in C++ to even consider using unions (their use may be very dangerous and must be thought of carefully), you could look up the rules for unions in the standard yourself :).

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

Now that we know what's special about aggregates, let's try to understand the restrictions on classes; that is, why they are there. We should understand that memberwise initialization with braces implies that the class is nothing more than the sum of its members. If a user-defined constructor is present, it means that the user needs to do some extra work to initialize the members therefore brace initialization would be incorrect. If virtual functions are present, it means that the objects of this class have (on most implementations) a pointer to the so-called vtable of the class, which is set in the constructor, so brace-initialization would be insufficient. You could figure out the rest of the restrictions in a similar manner as an exercise :).

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

So enough about the aggregates. Now we can define a stricter set of types, to wit, PODs

关于聚合就足够了。现在我们可以定义一组更严格的类型,即 POD

What are PODs and why they are special

什么是 POD 以及为什么它们很特别

Formal definition from the C++ standard (C++03 9 §4):

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.

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

Wow, this one's tougher to parse, isn't it? :) Let's leave unions out (on the same grounds as above) and rephrase in a bit clearer way:

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

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.

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

What does this definition imply? (Did I mention PODstands for Plain Old Data?)

这个定义意味着什么?(我有没有提到POD代表普通旧数据?)

  • All POD classes are aggregates, or, to put it the other way around, if a class is not an aggregate then it is sure not a POD
  • Classes, just like structs, can be PODs even though the standard term is POD-struct for both cases
  • Just like in the case of aggregates, it doesn't matter what static members the class has
  • 所有 POD 类都是聚合,或者反过来说,如果一个类不是聚合,那么它肯定不是 POD
  • 类,就像结构一样,可以是 POD,即使两种情况的标准术语都是 POD-struct
  • 就像在聚合的情况下一样,类具有哪些静态成员并不重要

Examples:

例子:

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-classes, POD-unions, scalar types, and arrays of such types are collectively called POD-types.
PODs are special in many ways. I'll provide just some examples.

POD 类、POD 联合、标量类型和此类类型的数组统称为POD 类型。
POD 在很多方面都很特别。我将仅提供一些示例。

  • POD-classes are the closest to C structs. Unlike them, PODs can have member functions and arbitrary static members, but neither of these two change the memory layout of the object. So if you want to write a more or less portable dynamic library that can be used from C and even .NET, you should try to make all your exported functions take and return only parameters of POD-types.

  • The lifetime of objects of non-POD class type begins when the constructor has finished and ends when the destructor has finished. For POD classes, the lifetime begins when storage for the object is occupied and finishes when that storage is released or reused.

  • For objects of POD types it is guaranteed by the standard that when you memcpythe contents of your object into an array of char or unsigned char, and then memcpythe contents back into your object, the object will hold its original value. Do note that there is no such guarantee for objects of non-POD types. Also, you can safely copy POD objects with memcpy. The following example assumes T is a POD-type:

    #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 statement. As you may know, it is illegal (the compiler should issue an error) to make a jump via goto from a point where some variable was not yet in scope to a point where it is already in scope. This restriction applies only if the variable is of non-POD type. In the following example f()is ill-formed whereas g()is well-formed. Note that Microsoft's compiler is too liberal with this rule—it just issues a warning in both cases.

    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;
    }
    
  • It is guaranteed that there will be no padding in the beginning of a POD object. In other words, if a POD-class A's first member is of type T, you can safely reinterpret_castfrom A*to T*and get the pointer to the first member and vice versa.

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

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

  • 对于 POD 类型的对象,标准保证当您memcpy将对象的内容转换为 char 或 unsigned char 数组,然后memcpy将内容返回到您的对象时,该对象将保持其原始值。请注意,对于非 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 从某个变量尚未在范围内的点跳转到它已经在范围内的点是非法的(编译器应该发出错误)。此限制仅适用于变量为非 POD 类型的情况。在下面的示例中,格式错误f()g()格式良好。请注意,Microsoft 的编译器对这条规则过于宽松——它只是在两种情况下都发出警告。

    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 类型,您可以安全地reinterpret_castA*toT*获取指向第一个成员的指针,反之亦然。

The list goes on and on…

名单还在继续……

Conclusion

结论

It is important to understand what exactly a POD is because many language features, as you see, behave differently for them.

了解 POD 究竟是什么很重要,因为如您所见,许多语言功能对它们的表现各不相同。

回答by R. Martinho Fernandes

What changes for C++11?

C++11 有哪些变化?

Aggregates

聚合体

The standard definition of an aggregate has changed slightly, but it's still pretty much the same:

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

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal-initializersfor 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).

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

Ok, what changed?

好的,有什么变化?

  1. Previously, an aggregate could have no user-declaredconstructors, but now it can't have user-providedconstructors. Is there a difference? Yes, there is, because now you can declare constructors and defaultthem:

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

    This is still an aggregate because a constructor (or any special member function) that is defaulted on the first declarationis not user-provided.

  2. Now an aggregate cannot have any brace-or-equal-initializersfor non-static data members. What does this mean? Well, this is just because with this new standard, we can initialize members directly in the class like this:

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

    Using this feature makes the class no longer an aggregate because it's basically equivalent to providing your own default constructor.

  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
    };
    

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

So, what is an aggregate didn't change much at all. It's still the same basic idea, adapted to the new features.

所以,什么是聚合根本没有太大变化。它仍然是相同的基本思想,适应了新功能。

What about PODs?

POD 呢?

PODs went through a lot of changes. Lots of previous rules about PODs were relaxed in this new standard, and the way the definition is provided in the standard was radically changed.

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

The idea of a POD is to capture basically two distinct properties:

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

  1. It supports static initialization, and
  2. Compiling a POD in C++ gives you the same memory layout as a struct compiled in C.
  1. 它支持静态初始化,并且
  2. 在 C++ 中编译 POD 为您提供与在 C 中编译的结构相同的内存布局。

Because of this, the definition has been split into two distinct concepts: trivialclasses and standard-layoutclasses, because these are more useful than POD. The standard now rarely uses the term POD, preferring the more specific trivialand standard-layoutconcepts.

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

The new definition basically says that a POD is a class that is both trivial and has standard-layout, and this property must hold recursively for all non-static data members:

新定义基本上是说 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.

POD 结构是一个非联合类,它既是普通类又是标准布局类,并且没有非 POD 结构、非 POD 联合(或此类类型的数组)类型的非静态数据成员。类似地,POD 联合是一个联合,它既是普通类又是标准布局类,并且没有非 POD 结构、非 POD 联合(或此类类型的数组)类型的非静态数据成员。POD 类是一个既可以是 POD 结构体也可以是 POD 联合体的类。

Let's go over each of these two properties in detail separately.

让我们分别详细介绍这两个属性中的每一个。

Trivial classes

琐碎类

Trivialis the first property mentioned above: trivial classes support static initialization. If a class is trivially copyable (a superset of trivial classes), it is ok to copy its representation over the place with things like memcpyand expect the result to be the same.

Trivial是上面提到的第一个属性:trivial 类支持静态初始化。如果一个类是可简单复制的(一个简单类的超集),可以将它的表示复制到类似的地方,memcpy并期望结果相同。

The standard defines a trivial class as follows:

该标准定义了一个平凡的类,如下所示:

A trivially copyable class is a class that:

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

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

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

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

— has a trivial destructor (12.4).

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

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

一个普通的可复制类是这样一个类:

— 没有非平凡的复制构造函数 (12.8),

— 没有非平凡的移动构造函数 (12.8),

— 没有非平凡的复制赋值运算符 (13.5.3, 12.8),

— 没有重要的移动赋值运算符 (13.5.3, 12.8),和

— 有一个简单的析构函数 (12.4)。

平凡类是具有平凡默认构造函数 (12.1) 且可简单复制的类。

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

So, what are all those trivial and non-trivial things?

那么,那些琐碎和不平凡的事情是什么?

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

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

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

— 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;

otherwise the copy/move constructor is non-trivial.

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

— 类 X 没有虚函数(10.3)和虚基类(10.1),以及

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

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

否则复制/移动构造函数是重要的。

Basically this means that a copy or move constructor is trivial if it is not user-provided, the class has nothing virtual in it, and this property holds recursively for all the members of the class and for the base class.

基本上,这意味着如果不是用户提供的复制或移动构造函数是微不足道的,类中没有任何虚拟的东西,并且该属性对类的所有成员和基类递归保持。

The definition of a trivial copy/move assignment operator is very similar, simply replacing the word "constructor" with "assignment operator".

普通复制/移动赋值运算符的定义非常相似,只是将“构造函数”一词替换为“赋值运算符”。

A trivial destructor also has a similar definition, with the added constraint that it can't be virtual.

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

And yet another similar rule exists for trivial default constructors, with the addition that a default constructor is not-trivial if the class has non-static data members with brace-or-equal-initializers, which we've seen above.

对于平凡的默认构造函数,还有另一个类似的规则,此外,如果类具有带有括号或相等初始化器的非静态数据成员,则默认构造函数不是平凡的,我们在上面看到过。

Here are some examples to clear everything up:

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

// 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
};

Standard-layout

标准布局

Standard-layoutis the second property. The standard mentions that these are useful for communicating with other languages, and that's because a standard-layout class has the same memory layout of the equivalent C struct or union.

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

This is another property that must hold recursively for members and all base classes. And as usual, no virtual functions or virtual base classes are allowed. That would make the layout incompatible with C.

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

A relaxed rule here is that standard-layout classes must have all non-static data members with the same access control. Previously these had to be all public, but now you can make them private or protected, as long as they are allprivate or allprotected.

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

When using inheritance, only oneclass 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), otherwise, it's not a standard-layout class.

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

This is how the definition goes in the standard text:

这是标准文本中定义的方式:

A standard-layout class is a class that:

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

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

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

— has no non-standard-layout base classes,

— 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

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

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

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

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

标准布局类是这样一个类:

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

— 没有虚函数 (10.3) 和虚基类 (10.1),

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

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

— 要么在最派生的类中没有非静态数据成员且至多有一个具有非静态数据成员的基类,要么没有具有非静态数据成员的基类,并且

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

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

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

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

And let's see a few examples.

让我们看几个例子。

// 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

Conclusion

结论

With these new rules a lot more types can be PODs now. And even if a type is not POD, we can take advantage of some of the POD properties separately (if it is only one of trivial or standard-layout).

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

The standard library has traits to test these properties in the header <type_traits>:

标准库具有在头文件中测试这些属性的特性<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;

回答by Shafik Yaghmour

What has changed for C++14

C++14 发生了什么变化

We can refer to the Draft C++14 standardfor reference.

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

Aggregates

聚合体

This is covered in section 8.5.1Aggregateswhich gives us the following definition:

这在8.5.1Aggregates一节中有介绍,它为我们提供了以下定义:

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).

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

The only change is now adding in-class member initializersdoes not make a class a non-aggregate. So the following example from C++11 aggregate initialization for classes with member in-pace initializers:

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

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

was not an aggregate in C++11 but it is in C++14. This change is covered in N3605: Member initializers and aggregates, which has the following abstract:

不是 C++11 中的聚合,而是 C++14 中的聚合。N3605: Member initializers and aggregates 中涵盖了此更改,其中包含以下摘要:

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.

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

POD stays the same

POD 保持不变

The definition for POD(plain old data) struct is covered in section 9Classeswhich says:

POD( plain old data) 结构的定义在9Classes部分有介绍,它说:

A POD struct110is 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.

POD结构110是非联合类,它既是平凡类又是标准布局类,并且没有非POD结构、非POD联合(或此类类型的数组)类型的非静态数据成员。类似地,POD 联合是一个既是普通类又是标准布局类的联合,并且没有非 POD 结构、非 POD 联合(或此类类型的数组)类型的非静态数据成员。POD 类是一个既可以是 POD 结构体也可以是 POD 联合体的类。

which is the same wording as C++11.

这与 C++11 的措辞相同。

Standard-Layout Changes for C++14

C++14 的标准布局更改

As noted in the comments podrelies on the definition of standard-layoutand that did change for C++14 but this was via defect reports that were applied to C++14 after the fact.

正如评论中所指出的,pod依赖于标准布局的定义,并且对于 C++14 确实发生了变化,但这是通过在事后应用于 C++14 的缺陷报告。

There were three DRs:

共有三个 DR:

So standard-layoutwent from this Pre C++14:

所以标准布局来自这个 Pre 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

标准布局类是这样一个类:

  • (7.1) 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员,
  • (7.2) 没有虚函数([class.virtual])和虚基类([class.mi]),
  • (7.3) 对所有非静态数据成员具有相同的访问控制(条款 [class.access]),
  • (7.4) 没有非标准布局的基类,
  • (7.5) 要么在最派生的类中没有非静态数据成员且至多一个具有非静态数据成员的基类,要么没有具有非静态数据成员的基类,并且
  • (7.6) 没有与第一个非静态数据成员相同类型的基类。 109

To this in C++14:

为了在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.

类 S 是标准布局类,如果它:

  • (3.1) 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员,
  • (3.2) 没有虚函数和虚基类,
  • (3.3) 对所有非静态数据成员具有相同的访问控制,
  • (3.4) 没有非标准布局的基类,
  • (3.5) 至多有一个任何给定类型的基类子对象,
  • (3.6) 类中的所有非静态数据成员和位域及其基类首先在同一个类中声明,并且
  • (3.7) 没有类型集合 M(S) 的元素作为基类,其中对于任何类型 X,M(X) 定义如下。 104 [?注:M(X) 是类型的集合在 X 中可能处于零偏移量的所有非基类子对象中。—?尾注?]
    • (3.7.1) 如果 X 是没有(可能继承的)非静态数据成员的非联合类类型,则集合 M(X) 为空。
    • (3.7.2) 如果 X 是非联合类类型,其类型 X0 的非静态数据成员要么为零大小要么是 X 的第一个非静态数据成员(其中所述成员可能是匿名联合),集合 M(X) 由 X0 和 M(X0) 的元素组成。
    • (3.7.3) 如果 X 是联合类型,则集合 M(X) 是所有 M(Ui) 和包含所有 Ui 的集合的并集,其中每个 Ui 是 X 的第 i 个非静态数据成员的类型.
    • (3.7.4) 如果 X 是元素类型为 Xe 的数组类型,则集合 M(X) 由 Xe 和 M(Xe) 的元素组成。
    • (3.7.5) 如果 X 是非类、非数组类型,则集合 M(X) 为空。

回答by Nicol Bolas

can you please elaborate following rules:

能否请您详细说明以下规则:

I'll try:

我会尽力:

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

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

That's simple: all non-static data members must allbe public, private, or protected. You can't have some publicand some private.

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

The reasoning for them goes to the reasoning for having a distinction between "standard layout" and "not standard layout" at all. Namely, to give the compiler the freedom to choose how to put things into memory. It's not just about vtable pointers.

对它们的推理归结为根本区分“标准布局”和“非标准布局”的推理。也就是说,让编译器可以自由选择如何将东西放入内存。这不仅仅是关于 vtable 指针。

Back when they standardized C++ in 98, they had to basically predict how people would implement it. While they had quite a bit of implementation experience with various flavors of C++, they weren't certain about things. So they decided to be cautious: give the compilers as much freedom as possible.

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

That's why the definition of POD in C++98 is so strict. It gave C++ compilers great latitude on member layout for most classes. Basically, POD types were intended to be special cases, something you specifically wrote for a reason.

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

When C++11 was being worked on, they had a lot more experience with compilers. And they realized that... C++ compiler writers are really lazy. They had all this freedom, but they didn't doanything with it.

在开发 C++11 时,他们在编译器方面有更多的经验。他们意识到…… C++ 编译器编写者真的很懒惰。他们拥有所有这些自由,但他们没有任何事情。

The rules of standard layout are more or less codifying common practice: most compilers didn't really have to change much if anything at all to implement them (outside of maybe some stuff for the corresponding type traits).

标准布局的规则或多或少编纂了常见的做法:大多数编译器实际上并没有真正需要改变太多来实现它们(除了对应类型特征的一些东西)。

Now, when it came to public/private, things are different. The freedom to reorder which members are publicvs. privateactually can matter to the compiler, particularly in debugging builds. And since the point of standard layout is that there is compatibility with other languages, you can't have the layout be different in debug vs. release.

现在,当谈到public/ 时private,情况就不一样了。重新排序成员public与成员的自由private实际上对编译器很重要,特别是在调试构建中。由于标准布局的重点是与其他语言的兼容性,因此在调试与发布时布局不能不同。

Then there's the fact that it doesn't really hurt the user. If you're making an encapsulated class, odds are good that all of your data members will be privateanyway. You generally don't expose public data members on fully encapsulated types. So this would only be a problem for those few users who do want to do that, who want that division.

还有一个事实是它并没有真正伤害用户。如果您正在制作一个封装的类,那么您的所有数据成员private无论如何都是好的。您通常不会在完全封装的类型上公开公共数据成员。所以这只会对那些想要这样做的少数用户来说是一个问题,他们想要这种划分。

So it's no big loss.

所以损失不大。

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

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

The reason for this one comes back to why they standardized standard layout again: common practice.

这个原因又回到了为什么他们再次标准化标准布局:惯例。

There's nocommon practice when it comes to having two members of an inheritance tree that actually store things. Some put the base class before the derived, others do it the other way. Which way do you order the members if they come from two base classes? And so on. Compilers diverge greatly on these questions.

没有普遍的做法,当谈到有实际存储的东西继承树的两个成员。有些将基类放在派生类之前,有些则相反。如果成员来自两个基类,您如何对成员进行排序?等等。编译器在这些问题上分歧很大。

Also, thanks to the zero/one/infinity rule, once you say you can have two classes with members, you can say as many as you want. This requires adding a lot of layout rules for how to handle this. You have to say how multiple inheritance works, which classes put their data before other classes, etc. That's a lot of rules, for very little material gain.

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

You can't make everything that doesn't have virtual functions and a default constructor standard layout.

你不能让所有没有虚函数和默认构造函数的标准布局。

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

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

I can't really speak to this one. I'm not educated enough in C++'s aliasing rules to really understand it. But it has something to do with the fact that the base member will share the same address as the base class itself. That is:

我真的不能和这个说话。我在 C++ 的别名规则方面没有受过足够的教育,无法真正理解它。但这与基类成员将与基类本身共享相同地址的事实有关。那是:

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

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

And that's probably against C++'s aliasing rules. In some way.

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

However, consider this: how useful could having the ability to do this ever actuallybe? Since only one class can have non-static data members, then Derivedmust be that class (since it has a Baseas a member). So Basemustbe empty (of data). And if Baseis empty, as well asa base class... why have a data member of it at all?

但是,请考虑一下:能够做到这一点实际上有多大用处?因为只有一个类可以有非静态数据成员,所以Derived必须是那个类(因为它有Base一个成员)。所以Base必须是空的(数据)。如果Base是空的,还有一个基类......为什么要拥有它的数据成员呢?

Since Baseis empty, it has no state. So any non-static member functions will do what they do based on their parameters, not their thispointer.

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

So again: no big loss.

再说一遍:没有大的损失。

回答by ThomasMcLeod

Changes in C++17

C++17 中的变化

Download the C++17 International Standard final draft here.

此处下载 C++17 国际标准最终草案。

Aggregates

聚合体

C++17 expands and enhances aggregates and aggregate initialization. The standard library also now includes an std::is_aggregatetype trait class. Here is the formal definition from section 11.6.1.1 and 11.6.1.2 (internal references elided):

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.

聚合是一个数组或类,
没有用户提供的、显式的或继承的构造函数,
没有私有或受保护的非静态数据成员,
没有虚函数,并且
没有虚拟、私有或受保护的基类。
[ 注意:聚合初始化不允许访问受保护和私有基类的成员或构造函数。— 尾注 ]
聚合的元素是:
— 对于数组,按递增下标顺序排列的数组元素,或
— 对于类,按声明顺序排列的直接基类,后跟非静态数据成员的直接非静态数据成员匿名联合的成员,按声明顺序。

What changed?

发生了什么变化?

  1. Aggregates can now have public, non-virtual base classes. Furthermore, it is not a requirement that base classes be aggregates. If they are not aggregates, they are list-initialized.
  1. 聚合现在可以拥有公共的、非虚拟的基类。此外,并不要求基类是聚合体。如果它们不是聚合,则它们是列表初始化的。
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
    << "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. Explicit defaulted constructors are disallowed
  1. 不允许使用显式默认构造函数
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Inheriting constructors are disallowed
  1. 不允许继承构造函数
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Trivial Classes


琐碎类

The definition of trivial class was reworked in C++17 to address several defects that were not addressed in C++14. The changes were technical in nature. Here is the new definition at 12.0.6 (internal references elided):

平凡类的定义在 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 ]

一个普通可复制的类是这样一个类:
— 其中每个复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都被删除或平凡,
— 至少有一个未删除的复制构造函数、移动构造函数、复制赋值运算符,或移动赋值运算符,并且
— 有一个简单的、未删除的析构函数。
平凡类是可平凡复制并具有一个或多个默认构造函数的类,所有这些构造函数都是平凡的或已删除的,并且至少有一个未被删除。[注意:特别是,一个平凡的可复制或平凡的类没有虚函数或虚基类。-尾注]

Changes:

变化:

  1. Under C++14, for a class to be trivial, the class could not have any copy/move constructor/assignment operators that were non-trivial. However, then an implicitly declaredas defaulted constructor/operator could be non-trivial and yet definedas deleted because, for example, the class contained a subobject of class type that could not be copied/moved. The presence of such non-trivial, defined-as-deleted constructor/operator would cause the whole class to be non-trivial. A similar problem existed with destructors. C++17 clarifies that the presence of such constructor/operators does not cause the class to be non-trivially copyable, hence non-trivial, and that a trivially copyable class must have a trivial, non-deleted destructor. DR1734, DR1928
  2. C++14 allowed a trivially copyable class, hence a trivial class, to have every copy/move constructor/assignment operator declared as deleted. If such as class was also standard layout, it could, however, be legally copied/moved with std::memcpy. This was a semantic contradiction, because, by defining as deleted all constructor/assignment operators, the creator of the class clearly intended that the class could not be copied/moved, yet the class still met the definition of a trivially copyable class. Hence in C++17 we have a new clause stating that trivially copyable class must have at least one trivial, non-deleted (though not necessarily publicly accessible) copy/move constructor/assignment operator. See N4148, DR1734
  3. The third technical change concerns a similar problem with default constructors. Under C++14, a class could have trivial default constructors that were implicitly defined as deleted, yet still be a trivial class. The new definition clarifies that a trivial class must have a least one trivial, non-deleted default constructor. See DR1496
  1. 在 C++14 下,对于平凡的类,该类不能有任何非平凡的复制/移动构造函数/赋值运算符。但是,隐式声明为默认构造函数/运算符可能是非平凡的,但仍被定义为已删除,因为例如,该类包含无法复制/移动的类类型的子对象。这种非平凡的、定义为已删除的构造函数/运算符的存在将导致整个类变得不平凡。析构函数也存在类似的问题。C++17 阐明了此类构造函数/运算符的存在不会导致类不可复制,因此是非平凡的,并且可平凡复制的类必须具有平凡的、未删除的析构函数。DR1734, DR1928
  2. C++14 允许一个平凡的可复制类,因此一个平凡的类,将每个复制/移动构造函数/赋值运算符声明为已删除。但是,如果此类类也是标准布局,则可以使用std::memcpy. 这是语义上的矛盾,因为通过将所有构造函数/赋值运算符定义为已删除,类的创建者清楚地希望该类不能被复制/移动,但该类仍然满足简单可复制类的定义。因此,在 C++17 中,我们有一个新子句,说明可简单复制的类必须至少有一个简单的、未删除的(尽管不一定是可公开访问的)复制/移动构造函数/赋值运算符。见N4148DR1734
  3. 第三个技术变化涉及默认构造函数的类似问题。在 C++14 下,一个类可以具有被隐式定义为已删除的平凡默认构造函数,但仍然是一个平凡的类。新定义阐明了一个平凡的类必须至少有一个平凡的、未删除的默认构造函数。见DR1496

Standard-layout Classes

标准布局类

The definition of standard-layout was also reworked to address defect reports. Again the changes were technical in nature. Here is the text from the standard (12.0.7). As before, internal references are elided:

标准布局的定义也经过重新设计以解决缺陷报告。同样,这些变化本质上是技术性的。这是标准 (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:

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.

类 S 是标准布局类,如果它:
— 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员,
— 没有虚函数和虚基类,
—对所有非静态数据成员具有相同的访问控制,
— 没有非标准布局的基类,
— 至多有一个任何给定类型的基类子对象,
— 具有所有非静态数据成员和位域该类及其基类首先在同一类中声明,并且
— 没有类型集 M(S) 的元素(定义如下)作为基类。 108
M(X) 定义如下:
— 如果 X 是没有(可能继承的)非静态数据成员的非联合类类型,则集合 M(X) 为空。
— 如果 X 是非联合类类型,其第一个非静态数据成员的类型为 X0(其中所述成员可能是匿名联合),则集合 M(X) 由 X0 和 M(X0) 的元素组成。
— 如果 X 是联合类型,则集合 M(X) 是所有 M(Ui) 和包含所有 Ui 的集合的并集,其中每个 Ui 是 X 的第 i 个非静态数据成员的类型。
— 如果 X是元素类型为 Xe 的数组类型,集合 M(X) 由 Xe 和 M(Xe) 的元素组成。
— 如果 X 是非类、非数组类型,则集合 M(X) 为空。
[注:M(X) 是所有非基类子对象的类型集合,这些子对象在标准布局类中保证在 X 中的偏移为零。--尾注]
[ 示例:

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) 这确保了两个具有相同类类型并且属于同一个最派生对象的子对象不会分配在相同的地址。

Changes:

变化:

  1. Clarified that the requirement that only one class in the derivation tree "has" non-static data members refers to a class where such data members are first declared, not classes where they may be inherited, and extended this requirement to non-static bit fields. Also clarified that a standard-layout class "has at most one base class subobject of any given type." See DR1813, DR1881
  2. The definition of standard-layout has never allowed the type of any base class to be the same type as the first non-static data member. It is to avoid a situation where a data member at offset zero has the same type as any base class. The C++17 standard provides a more rigorous, recursive definition of "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" so as to prohibit such types from being the type of any base class. See DR1672, DR2120.
  1. 阐明派生树中只有一个类“具有”非静态数据成员的要求是指首先声明此类数据成员的类,而不是可以继承它们的类,并将此要求扩展到非静态位字段. 还阐明了标准布局类“最多具有任何给定类型的一个基类子对象”。见DR1813DR1881
  2. 标准布局的定义从未允许任何基类的类型与第一个非静态数据成员的类型相同。这是为了避免偏移量为零的数据成员与任何基类具有相同类型的情况。C++17标准提供了更严格的递归定义“所有非基类子对象的类型集合,这些类型在标准布局类中保证为零偏移”,以禁止此类类型从成为任何基类的类型。参见DR1672DR2120

Note:The C++ standards committee intended the above changes based on defect reports to apply to C++14, though the new language is not in the published C++14 standard. It is in the C++17 standard.

注意:C++ 标准委员会打算将基于缺陷报告的上述更改应用于 C++14,尽管新语言不在已发布的 C++14 标准中。它在 C++17 标准中。

回答by Barry

What changes in c++20

c++20有什么变化

Following the rest of the clear theme of this question, the meaning and use of aggregates continues to change with every standard. There are several key changes on the horizon.

遵循这个问题的其余明确主题,聚合的含义和使用继续随着每个标准而变化。有几个关键的变化即将发生。

Types with user-declared constructors P1008

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

In C++17, this type is still an aggregate:

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

struct X {
    X() = delete;
};

And hence, X{}still compiles because that is aggregate initialization - not a constructor invocation. See also: When is a private constructor not a private constructor?

因此,X{}仍然可以编译,因为这是聚合初始化 - 而不是构造函数调用。另请参阅:何时私有构造函数不是私有构造函数?

In C++20, the restriction will change from requiring:

在 C++20 中,限制将从要求更改为:

no user-provided, explicit, or inherited constructors

没有用户提供的explicit、或继承的构造函数

to

no user-declared or inherited constructors

没有用户声明或继承的构造函数

This has been adopted into the C++20 working draft. Neither the Xhere nor the Cin the linked question will be aggregates in C++20.

这已被采纳到C++20 工作草案中。链接问题中的Xhere 和Cin都不是C++20 中的聚合。

This also makes for a yo-yo effect with the following example:

这也可以通过以下示例产生溜溜球效果:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

In C++11/14, Bwas notan aggregate due to the base class, so B{}performs value-initialization which calls B::B()which calls A::A(), at a point where it is accessible. This was well-formed.

在 C++11/14 中,由于基类B不是聚合,因此B{}在可访问的点执行调用B::B()which 调用的值初始化A::A()。这是格式良好的。

In C++17, Bbecame an aggregate because base classes were allowed, which made B{}aggregate-initialization. This requires copy-list-initializing an Afrom {}, but from outside the context of B, where it is not accessible. In C++17, this is ill-formed (auto x = B();would be fine though).

在 C++17 中,B因为允许基类而成为聚合,从而进行B{}聚合初始化。这需要复制列表初始化Afrom {},但是从 的上下文之外B,在那里它是不可访问的。在 C++17 中,这是格式错误的(auto x = B();虽然很好)。

In C++20 now, because of the above rule change, Bonce again ceases to be an aggregate (not because of the base class, but because of the user-declared default constructor - even though it's defaulted). So we're back to going through B's constructor, and this snippet becomes well-formed.

现在在 C++20 中,由于上述规则更改,B再次不再是聚合(不是因为基类,而是因为用户声明的默认构造函数 - 即使它是默认的)。所以我们回到通过B的构造函数,并且这个片段变得格式良好。

Initializing aggregates from a parenthesized list of values P960

从括号中的值列表初始化聚合P960

A common issue that comes up is wanting to use emplace()-style constructors with aggregates:

出现的一个常见问题是希望将emplace()-style 构造函数与聚合一起使用:

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

This does not work, because emplacewill try to effectively perform the initialization X(1, 2), which is not valid. The typical solution is to add a constructor to X, but with this proposal (currently working its way through Core), aggregates will effectively have synthesized constructors which do the right thing - and behave like regular constructors. The above code will compile as-is in C++20.

这不起作用,因为emplace会尝试有效地执行初始化X(1, 2),这是无效的。典型的解决方案是向 中添加一个构造函数X,但是通过这个提议(目前正在通过 Core 工作),聚合将有效地具有合成的构造函数,它们可以做正确的事情 - 并且表现得像常规构造函数。上面的代码将在 C++20 中按原样编译。

Class Template Argument Deduction (CTAD) for Aggregates P1021(specifically P1816)

聚合P1021(特别是P1816)的类模板参数推导 ( CTAD)

In C++17, this does not compile:

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

template <typename T>
struct Point {
    T x, y;
};

Point p{1, 2}; // error

Users would have to write their own deduction guide for all aggregate templates:

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

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

But as this is in some sense "the obvious thing" to do, and is basically just boilerplate, the language will do this for you. This example will compile in C++20 (without the need for the user-provided deduction guide).

但是,由于这在某种意义上是“显而易见的事情”,并且基本上只是样板文件,因此该语言将为您做到这一点。此示例将在 C++20 中编译(无需用户提供的推导指南)。