这个常见问题是关于聚合和PODs的,包括以下材料:
什么是聚合? 什么是pod(普通旧数据)? 它们之间有什么关系? 他们有什么特别之处? c++ 11有什么变化?
这个常见问题是关于聚合和PODs的,包括以下材料:
什么是聚合? 什么是pod(普通旧数据)? 它们之间有什么关系? 他们有什么特别之处? c++ 11有什么变化?
当前回答
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标准中。
其他回答
阅读方法:
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 may jump 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.
这个答案适用于c++ 03。有关其他c++标准,请参阅:
c++ 11更改 c++ 14日变化 c++ 17日变化 C + + 20的变化
什么是聚合?为什么它们是特殊的
来自c++标准(c++ 03 8.5.1§1)的正式定义:
聚合是没有用户声明的数组或类(第9条) 构造函数(12.1),没有私有或受保护的非静态数据成员(第11条), 没有基类(第10节),也没有虚函数(10.3节)。
好,我们来解析一下这个定义。首先,任何数组都是一个聚合。一个类也可以是一个聚合,如果……等等!没有提到结构体或并集,难道它们不能是集合吗?是的,他们可以。在c++中,术语类指的是所有的类、结构体和联合。因此,当且仅当一个类(或结构或联合)满足上述定义中的条件时,它才是一个聚合。这些标准意味着什么?
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 static data 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.
现在我们来看一些例子:
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
};
你懂的。现在让我们看看聚合的特殊之处。与非聚合类不同,它们可以用花括号{}进行初始化。这种初始化语法通常用于数组,我们刚刚了解到这些是聚合。让我们从它们开始。
类型array_name[n] = {a1, a2,…,am};
if(m == n) the ith element of the array is initialized with ai else if(m < n) the first m elements of the array are initialized with a1, a2, …, am and the other n - m elements 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};
When an object of scalar type (bool, int, char, double, pointers, etc.) is value-initialized it means it is initialized with 0 for that type (false for bool, 0.0 for 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.
数组初始化的例子:
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}};
在上面的例子中,y.c是用'a'初始化的,y.x.i1是用10初始化的,y.x.i2是用20初始化的,y.i.[0]是用20初始化的,y.i.[1]是用30初始化的,y.f是用0.0初始化的。受保护的静态成员d根本没有初始化,因为它是静态的。
聚合联合的不同之处在于只能用大括号初始化它们的第一个成员。我认为如果你在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 :).
关于聚集体讲够了。现在我们可以定义一组更严格的类型,即pod
什么是pod ?为什么它们很特别
来自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 它没有用户定义的拷贝赋值 运算符和析构函数都没有 它的非静态成员是非pod 类、非pod数组或 参考。
这个定义意味着什么?(我提到过POD是Plain Old Data的缩写吗?)
所有POD类都是聚合,或者,换句话说,如果一个类不是聚合,那么它肯定不是POD 类,就像结构体一样,可以是pod,尽管这两种情况的标准术语都是POD-struct 就像在聚合的情况下一样,类有什么静态成员并不重要
例子:
struct POD
{
int x;
char y;
void f() {} //no harm if there's a function
static std::vector<char> v; //static members do not matter
};
struct AggregateButNotPOD1
{
int x;
~AggregateButNotPOD1() {} //user-defined destructor
};
struct AggregateButNotPOD2
{
AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};
pod -类、pod -联合、标量类型和此类类型的数组统称为pod -类型。 pod在很多方面都很特别。我将提供一些例子。
POD-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 memcpy the contents of your object into an array of char or unsigned char, and then memcpy the 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_cast from A* to T* and get the pointer to the first member and vice versa.
这样的例子不胜枚举……
结论
理解POD到底是什么很重要,因为正如您所看到的,许多语言特性对它们的行为是不同的。
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;
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++ 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++ 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标准中。