这个常见问题是关于聚合和PODs的,包括以下材料:
什么是聚合? 什么是pod(普通旧数据)? 它们之间有什么关系? 他们有什么特别之处? c++ 11有什么变化?
这个常见问题是关于聚合和PODs的,包括以下材料:
什么是聚合? 什么是pod(普通旧数据)? 它们之间有什么关系? 他们有什么特别之处? c++ 11有什么变化?
当前回答
c++20有什么变化
在这个问题的其余明确主题之后,聚合的含义和使用随着每个标准的变化而不断变化。有几个关键的变化即将出现。
带有用户声明的构造函数的类型
在c++ 17中,这个类型仍然是一个聚合:
struct X {
X() = delete;
};
因此,X{}仍然可以编译,因为这是聚合初始化—而不是构造函数调用。请参见:什么时候私有构造函数不是私有构造函数?
在c++ 20中,限制将从要求:
没有用户提供的、显式的或继承的构造函数
to
没有用户声明的或继承的构造函数
这已被纳入c++ 20工作草案。这里的X和链接问题中的C都不会在c++ 20中聚合。
下面的例子也会产生溜溜球效果:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
在c++ 11/14中,由于基类的原因,B不是一个聚合,所以B{}执行值初始化,调用B::B(),调用A::A(),在它可访问的位置。这是很好的形式。
在c++ 17中,B成为一个聚合,因为基类是允许的,这使得B{}聚合初始化。这需要从{}对A进行copy-list-初始化,但要在B的上下文之外进行初始化,因为B是不可访问的。在c++ 17中,这是格式错误的(auto x = B();不过也可以)。
在现在的c++ 20中,由于上述规则的改变,B再次不再是一个聚合(不是因为基类,而是因为用户声明的默认构造函数——尽管它是默认的)。所以我们回到了B的构造函数,这个代码段变成了格式良好的。
初始化从圆括号值P960的列表聚合
出现的一个常见问题是想要使用emplace()风格的构造函数与聚合:
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
这不起作用,因为emplace将尝试有效地执行初始化X(1,2),这是无效的。典型的解决方案是向X添加一个构造函数,但是根据这个提议(目前正在通过Core),聚合将有效地拥有做正确事情的合成构造函数——并且行为像常规构造函数一样。上面的代码将按原样在c++ 20中编译。
聚合P1021(特别是P1816)的类模板参数推断(CTAD)
在c++ 17中,这不能编译:
template <typename T>
struct Point {
T x, y;
};
Point p{1, 2}; // error
用户必须为所有聚合模板编写自己的演绎指南:
template <typename T> Point(T, T) -> Point<T>;
但由于这在某种意义上是“显而易见的事情”,而且基本上只是样板文件,所以语言会为您做这件事。这个例子将用c++ 20编译(不需要用户提供的演绎指南)。
其他回答
c++20有什么变化
在这个问题的其余明确主题之后,聚合的含义和使用随着每个标准的变化而不断变化。有几个关键的变化即将出现。
带有用户声明的构造函数的类型
在c++ 17中,这个类型仍然是一个聚合:
struct X {
X() = delete;
};
因此,X{}仍然可以编译,因为这是聚合初始化—而不是构造函数调用。请参见:什么时候私有构造函数不是私有构造函数?
在c++ 20中,限制将从要求:
没有用户提供的、显式的或继承的构造函数
to
没有用户声明的或继承的构造函数
这已被纳入c++ 20工作草案。这里的X和链接问题中的C都不会在c++ 20中聚合。
下面的例子也会产生溜溜球效果:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
在c++ 11/14中,由于基类的原因,B不是一个聚合,所以B{}执行值初始化,调用B::B(),调用A::A(),在它可访问的位置。这是很好的形式。
在c++ 17中,B成为一个聚合,因为基类是允许的,这使得B{}聚合初始化。这需要从{}对A进行copy-list-初始化,但要在B的上下文之外进行初始化,因为B是不可访问的。在c++ 17中,这是格式错误的(auto x = B();不过也可以)。
在现在的c++ 20中,由于上述规则的改变,B再次不再是一个聚合(不是因为基类,而是因为用户声明的默认构造函数——尽管它是默认的)。所以我们回到了B的构造函数,这个代码段变成了格式良好的。
初始化从圆括号值P960的列表聚合
出现的一个常见问题是想要使用emplace()风格的构造函数与聚合:
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
这不起作用,因为emplace将尝试有效地执行初始化X(1,2),这是无效的。典型的解决方案是向X添加一个构造函数,但是根据这个提议(目前正在通过Core),聚合将有效地拥有做正确事情的合成构造函数——并且行为像常规构造函数一样。上面的代码将按原样在c++ 20中编译。
聚合P1021(特别是P1816)的类模板参数推断(CTAD)
在c++ 17中,这不能编译:
template <typename T>
struct Point {
T x, y;
};
Point p{1, 2}; // error
用户必须为所有聚合模板编写自己的演绎指南:
template <typename T> Point(T, T) -> Point<T>;
但由于这在某种意义上是“显而易见的事情”,而且基本上只是样板文件,所以语言会为您做这件事。这个例子将用c++ 20编译(不需要用户提供的演绎指南)。
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.
改变了什么?
聚合现在可以有公共的非虚拟基类。此外,并不要求基类是聚合的。如果它们不是聚合,则是列表初始化的。
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
不允许显式的默认构造函数
struct D // not an aggregate
{
int i = 0;
D() = default;
explicit D(D const&) = default;
};
不允许继承构造函数
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 ]
变化:
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 declared as defaulted constructor/operator could be non-trivial and yet defined as 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 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 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
标准布局类
标准布局的定义也被重做以处理缺陷报告。同样,这些变化是技术性的。以下是来自标准(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.
变化:
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 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.
注意:c++标准委员会希望上述基于缺陷报告的更改适用于c++ 14,尽管新语言不在已发布的c++ 14标准中。它在c++ 17标准中。
c++ 14有什么变化
我们可以参考c++ 14标准草案。
聚合
这将在8.5.1节中介绍,其中给出了以下定义:
聚合是一个没有用户提供的数组或类(第9条) 构造函数(12.1),没有私有或保护的非静态数据成员 (第11条),没有基类(第10条),没有虚函数 (10.3)。
唯一的变化是现在添加类内成员初始化器不会使类成为非聚合。因此,下面的例子来自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++ 14的标准布局变化
正如在评论舱中提到的,依赖于标准布局的定义,这在c++ 14中确实发生了变化,但这是通过在事后应用于c++ 14的缺陷报告。
有三个dr:
1672年博士 1813年博士 2120年博士
标准布局从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
在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.
c++ 11中的POD基本上分为两个不同的轴:琐碎和布局。琐碎是关于一个对象的概念价值和它存储的数据位之间的关系。布局是关于…一个对象的子对象的布局。只有类类型有布局,而所有类型都有琐碎关系。
这就是琐碎轴的含义:
Non-trivially copyable: The value of objects of such types may be more than just the binary data that are stored directly within the object. For example, unique_ptr<T> stores a T*; that is the totality of the binary data within the object. But that's not the totality of the value of a unique_ptr<T>. A unique_ptr<T> stores either a nullptr or a pointer to an object whose lifetime is managed by the unique_ptr<T> instance. That management is part of the value of a unique_ptr<T>. And that value is not part of the binary data of the object; it is created by the various member functions of that object. For example, to assign nullptr to a unique_ptr<T> is to do more than just change the bits stored in the object. Such an assignment must destroy any object managed by the unique_ptr. To manipulate the internal storage of a unique_ptr without going through its member functions would damage this mechanism, to change its internal T* without destroying the object it currently manages, would violate the conceptual value that the object possesses. Trivially copyable: The value of such objects are exactly and only the contents of their binary storage. This is what makes it reasonable to allow copying that binary storage to be equivalent to copying the object itself. The specific rules that define trivial copyability (trivial destructor, trivial/deleted copy/move constructors/assignment) are what is required for a type to be binary-value-only. An object's destructor can participate in defining the "value" of an object, as in the case with unique_ptr. If that destructor is trivial, then it doesn't participate in defining the object's value. Specialized copy/move operations also can participate in an object's value. unique_ptr's move constructor modifies the source of the move operation by null-ing it out. This is what ensures that the value of a unique_ptr is unique. Trivial copy/move operations mean that such object value shenanigans are not being played, so the object's value can only be the binary data it stores. Trivial: This object is considered to have a functional value for any bits that it stores. Trivially copyable defines the meaning of the data store of an object as being just that data. But such types can still control how data gets there (to some extent). Such a type can have default member initializers and/or a default constructor that ensures that a particular member always has a particular value. And thus, the conceptual value of the object can be restricted to a subset of the binary data that it could store. Performing default initialization on a type that has a trivial default constructor will leave that object with completely uninitialized values. As such, a type with a trivial default constructor is logically valid with any binary data in its data storage.
布局轴非常简单。在决定类的子对象如何存储在类的存储中时,编译器有很大的余地。然而,在某些情况下,这种灵活性是不必要的,有更严格的顺序保证是有用的。
这样的类型是标准的布局类型。c++标准甚至没有具体说明这种布局是什么。它基本上说了关于标准布局类型的三件事:
第一个子对象与对象本身的地址相同。 可以使用offsetof来获取外部对象到其成员子对象的字节偏移量。 如果活动成员(至少部分)使用与被访问的非活动成员相同的布局,则联合可以通过非活动成员访问子对象。
编译器通常允许标准布局对象映射到C中具有相同成员的结构类型,但在c++标准中没有这样的语句;这正是编译器喜欢做的事情。
在这一点上,POD基本上是一个无用的术语。它只是普通的可复制性(值只是它的二进制数据)和标准布局(它的子对象的顺序定义更明确)的交集。人们可以从这些东西推断出该类型是C类的,并且可以映射到类似的C对象。但是标准并没有这样的表述。
请详细说明以下规则:
我将试一试:
A)标准布局类必须具有具有相同访问控制的所有非静态数据成员
这很简单:所有非静态数据成员都必须是公共的、私有的或受保护的。你不能既有公共的,也有私人的。
它们的原因在于“标准布局”和“非标准布局”之间的区别。也就是说,给编译器自由选择如何将东西放入内存。这不仅仅是虚表指针。
当他们在98年标准化c++时,他们必须基本上预测人们将如何实现它。虽然他们有相当多的c++实现经验,但他们对一些事情并不确定。所以他们决定谨慎一点:给编译器尽可能多的自由。
这就是为什么c++ 98中POD的定义如此严格。它让c++编译器在大多数类的成员布局上有很大的自由度。基本上,POD类型被设计为特殊情况,是出于某种原因而专门编写的。
当c++ 11还在开发的时候,他们有很多使用编译器的经验。他们意识到…c++编译器的编写者真的很懒。他们有这么多自由,却什么都没做。
标准布局的规则或多或少是对常见实践的编码:大多数编译器实际上不需要做太多改变来实现它们(可能除了一些对应类型特征的东西)。
现在,当涉及到公共/私人时,情况就不一样了。对公共成员和私有成员重新排序的自由实际上对编译器很重要,特别是在调试构建时。因为标准布局的重点是与其他语言兼容,你不能让布局在调试和发布中有所不同。
还有一个事实是,它并没有真正伤害用户。如果您正在创建一个封装类,那么您的所有数据成员都很可能是私有的。通常不会在完全封装的类型上公开公共数据成员。所以这只会成为少数想要这样做的用户的问题,他们想要这样划分。
所以损失不大。
B)整个继承树中只有一个类可以拥有非静态数据成员,
这个的原因又回到了他们标准化标准布局的原因:常见的做法。
当涉及到一个继承树中有两个实际存储内容的成员时,没有常见的实践。有些人把基类放在派生类之前,有些人则相反。如果成员来自两个基类,该如何排序?等等。编者们在这些问题上分歧很大。
此外,由于0 / 1 /无穷规则,一旦你说你可以有两个有成员的类,你就可以想说多少就说多少。这需要添加许多布局规则来处理这个问题。你必须说明多重继承是如何工作的,哪些类把它们的数据放在其他类之前,等等。这是一大堆规则,却只有很少的物质利益。
你不能让所有没有虚函数和默认构造函数的东西都成为标准布局。
第一个非静态数据成员不能是基类类型(这可能会破坏别名规则)。
我真的不能和这个说话。我对c++的别名规则没有足够的了解,无法真正理解它。但这与基类成员将与基类本身共享相同的地址有关。那就是:
struct Base {};
struct Derived : Base { Base b; };
Derived d;
static_cast<Base*>(&d) == &d.b;
这可能违反了c++的混叠规则。在某种程度上。
然而,考虑一下:拥有这样的能力到底有多大用处呢?因为只有一个类可以拥有非静态数据成员,那么Derived必须是那个类(因为它有一个Base作为成员)。因此Base必须为空(data)。如果Base是空的,以及一个基类…为什么要有数据成员呢?
因为Base是空的,所以它没有状态。所以任何非静态成员函数都是基于它们的形参,而不是this指针。
所以还是那句话:损失不大。
c++ 11有什么变化?
聚合
总量的标准定义略有变化,但它仍然基本相同:
聚合是一个没有用户提供构造函数(12.1)的数组或类(第9章), 对于非静态数据成员(9.2)没有括号或等号初始化器,没有private或protected 非静态数据成员(第11章),没有基类(第10章),没有虚函数(10.3章)。
有什么变化?
Previously, an aggregate could have no user-declared constructors, but now it can't have user-provided constructors. Is there a difference? Yes, there is, because now you can declare constructors and default them: 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 declaration is not user-provided. Now an aggregate cannot have any brace-or-equal-initializers for 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.
所以,总量并没有太大变化。它仍然是相同的基本理念,只是适应了新功能。
pod呢?
PODs经历了很多变化。在这个新标准中,许多以前关于pod的规则都被放宽了,标准中提供定义的方式也发生了根本的变化。
POD的思想基本上是捕捉两个不同的属性:
它支持静态初始化和 用c++编译POD会得到与用C编译的结构体相同的内存布局。
因此,定义被分为两个不同的概念:普通类和标准布局类,因为它们比POD更有用。标准现在很少使用术语POD,而更喜欢更具体的琐碎和标准布局概念。
新的定义基本上是说POD是一个平凡且具有标准布局的类,并且这个属性必须递归地用于所有非静态数据成员:
POD结构体是非联合类,它既是普通类,也是标准布局类, 并且没有非pod结构体、非pod联合(或此类类型的数组)类型的非静态数据成员。 类似地,POD联合是一个平凡类和标准布局类的联合,并且具有 没有非pod结构体、非pod联合(或此类类型的数组)类型的非静态数据成员。 POD类是一个POD结构体或POD联合的类。
让我们分别详细介绍这两个属性。
简单的类
平凡是上面提到的第一个属性:平凡类支持静态初始化。 如果一个类是普通可复制的(普通类的超集),那么可以使用memcpy之类的东西复制它的表示,并期望得到相同的结果。
标准定义了一个平凡类,如下所示:
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 ]
那么,这些琐碎和非琐碎的事情是什么呢?
类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语言不兼容。
这里的一个宽松规则是标准布局类必须具有具有相同访问控制的所有非静态数据成员。以前这些都必须是公开的,但现在你可以将它们设置为私有或受保护的,只要它们都是私有或受保护的。
在使用继承时,整个继承树中只有一个类可以具有非静态数据成员,并且第一个非静态数据成员不能是基类类型(这可能会破坏别名规则),否则,它就不是标准布局类。
以下是标准文本中的定义:
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 ]
我们来看几个例子。
// 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属性(如果它只是普通或标准布局中的一个)。
标准库有trait来测试头文件<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;