引言
在C++中,POD是一个极其重要的概念。要理解POD类型,我们首先需要理解Aggregate类型。下文根据stackoverflow上的回答将对Aggregate类型做一个全面的解读。
对于Aggragates的定义
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).
译文:一个Aggregate是一个数组或者一个没有用户声明构造函数,没有私有或保护类型的非静态数据成员,没有父类和虚函数的类型
从定义中我们首先可以解读出以下两点信息:
1. 数组一定是Aggregate。
2. 满足以下定义的class也可以是Aggregate(注意:class包括classes、structs和unions):
-> Aggregate不能拥有用户自定义的构造函数。事实上,它可以拥有一个默认构造函数或者一个默认复制构造函数,只要它们是被编译器声明,而非用户自定义的即可。
-> Aggregate不能拥有private或者protected的非静态数据成员。事实上,你可以定义其他private和protected成员方法(不包括构造函数,对于Aggregate,不能由用户自定义构造函数,参见上条),也可以定义private和protected静态类型的数据成员和方法,这都不会违背aggregate类型的规则。
-> Aggregate没有父类和虚函数。
-> Aggregate类型可以由用户自定义赋值操作符和析构函数。
注意:数组一定是Aggregate类型,即便数组中存放的是非Aggregate类型的元素。
例子
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 };
Why Aggregates are special?
与非Aggregate类型不同的是,Aggregate类型可以使用“{}”来进行初始化。这种初始化语法,实际上在数组中是很常见的。别忘了我们的数组类型也属于Aggregate,来看以下例子:
Type array_name[n] = {a1, a2, ..., am};
1. 如果m与n相等:很自然,数组的第i个元素被初始化为ai。
2. 如果m小于n :数组的前m个元素被依次初始化为a1,a2,…,am,剩余的n-m个元素将进行值初始化(前提是可以进行值初始化)。
3. 如果m大于n :产生编译错误。
注意,类似于以下形式也是正确的:
int a[] = {1,2,3};) // 等价于 int a[3] = {1,2,3};
数组的长度将由编译器推测为3,因此以上两种形式是等价的。
值初始化(value-initialization)
先来看以下解释,摘自stackoverflow:
When an object of scalar type (
bool
,int
,char
,double
, pointers, etc.) is value-initialized it means it is initialized with0
for that type (false
forbool
,0.0
fordouble
, 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.
译文如下:
对于标量类型(如:bool、int、char、double、指针)的对象如果按值初始化,是指其将被初始化为0(如:bool类型将被初始化为false,double类型将被初始化为0.0)。
默认构造函数由用户自定义的类类型对象如果按值初始化,那么其默认构造函数将会被调用;如果默认构造函数为隐式定义,那么所有的非静态数据成员将会递归地按值初始化。
虽然以上定义并不精确,也不完全,但是可以让我们有个基本的认识。注意,引用不能按值初始化。对于非Aggregate类型的class进行值初始化,可能会失败,例如没有合适的默认构函数。
1. 来看以下数组初始化的例子:
class A() { A(int){} // 用户自定义了构造函数,因此是非Aggrerate类型。注意,类A没有默认构造函数。定义对象时,无法进行值初始化 }; class B() { B() {} // 用户自定义了构造函数,因此是非Aggrerate类型。注意,类B拥有可用的默认构造函数。定义对象时,可以进行值初始化。 }; int main() { A a1[3] = {A(2), A(1), A(14)}; // OK n == m A a2[3] = {A(2)}; // ERROR A没有默认构造函数. 不能按值初始化a2[1] 和 a2[2] B b1[3] = {B()}; // OK b1[1]和b1[2]使用默认构造函数按值初始化 int Array1[1000] = {0}; // 所有元素被初始化为0 int Array2[1000] = {1}; // 注意: 只有第一个元素被初始化为1,其他为0; bool Array3[1000] = {}; // 大括号里可以为空,所有元素被初始化为false; int Array4[1000]; // 没有被初始化. 这和空{}初始化不同;这种情形下的元素没有按值初始化,他们的值是未知的,不确定的; (除非Array4是全局数据) int array[2] = {1,2,3,4}; // ERROR, 太多初始值,编译出错。 }
2. 现在我们来看Aggregates类类型是如何使用{ }进行初始化的。和对数组进行初始化非常类似,按照在类内部声明的顺序(按照定义都必须是public类型)初始化非静态类型的成员变量。如果初始值比成员少,那么其他的成员将按值初始化。如果有一个成员无法进行按值初始化,我们将会得到一个编译期错误。如果初始值比成员多,我们同样得到一个编译期错误。
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。protected类型的static数据成员d不会被初始化,因为它是静态类型的。
总结
我们知道了Aggregates的特别之处,现在让我们来尝试理解一下它对类型的限制,也就是说为什么会有这些限制。来看以下解释,同样摘自stackoverflow:
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的指针,需要在构造函数内设置,所以使用{ }初始化是不够的。我们可以按照这种方式理解其他限制的含义。
下篇博文,我们会通过Aggregates类型来介绍POD(原生数据类型)。
参考文献
1. What are Aggregates and PODs and how/why are they special?