【译】C++ POD的理解(2):PODs
什么是PODs以及它的特殊性
在C++标准中的正式定义(C++03 9 §4):
一个POD结构体是一个没有非静态的non-POD-struct、non-POD-union(或者这些类型的数组)或者引用类型的数据成员的aggregate类型,并且没有用户自定义的拷贝赋值操作和用户自定义的析构函数(译者注:这句话读起来很绕,在此解释一下,POD类型首先是一个aggregate类型,这个aggregate类型中不能含有非静态的non-POD结构体或者联合体,也不能含有用户自定义的拷贝赋值函数和用户自定义的析构函数)。类似的,一个POD联合体是一个没有非静态的non-POD-struct、non-POD-union(或者这些类型的数组)或者引用类型的数据成员的aggregate类型,并且没有用户自定义的拷贝赋值操作和用户自定义的析构函数。一个POD类要么是POD结构体,否则是POD联合体。
这个看起来更难以理解。现在让我们把联合体剥离,换一种更清晰的说法:
如果一个aggregate类型不含用户自定义的拷贝赋值函数和析构函数,不含有非静态的non-POD类型的结构体和no-POD类型结构体的数组,不含有引用成员,则这个aggreagate类型为POD类型。
这个定义是什么意思呢(POD就是Plain Old Data)?
- 所有的POD类型都是aggregates,反过来说,如果一个类不是aggregate,则它肯定不是POD;
- 与struct一样,class也可以是pod,尽管这两种情况下的标准术语都是POD-struct;
- 就像aggregate的例子一样,不受static成员影响。
举例:
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类型。
PODs有很多特殊的地方,我这里仅举几个例子:
- POD类型最接近C语言中的结构体。与他们不同的是,PODs可以拥有成员函数和任意类型的static成员变量,但是上述两者都不会改变对象的内存布局。所以如果你想或多或少写一个可以被C甚至是.NET调用的动态库,你应该尝试让所有的输入和返回类型都是POD类型;
- 在非POD类对象的生命周期内是从构造函数完成到析构函数完成。对于POD类来说,对象的生命周期是从对象的存储被占用开始到存储被释放或者重用结束;
- 对POD类型的对象来说,C++标准保证了当你使用
memcpy
将对象的内容拷贝到一个char
或者unsigned char
的数组中再使用memcpy
拷贝回来时,对象的内容保持不变。值得注意的是对于非POD类型来说,C++没有这样的保证。当然,你可以使用memcoy
在POD对象之间安全地拷贝。下面的例子中假设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语句。你可能知道,通过goto语句从某个变量还不存在的作用域的点上跳到它已经存在的作用域的点是非法的(编译器会报出错误)。这个限制仅适用于非POD类型的变量。在下面的例子中
f()
是不合法的,g()
是合法的。值得注意微软的编译器对于这条规则过于宽容--在所有例子中仅仅给出警告。
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;
}
- C++标准保证了POD类型的开头不会填充任何内容填充。换句话说,如果一个POD类型A的第一个成员是T类型的,你可以安全得从A*到T*进行reinterpret_cast,并且获得一个指针,反之亦然。
结论
理解POD的确切含义是很重要的,因为正如你所看到的,许多语言特性对它们的行为是不同的。
萌新一枚,有什么说得不对的地方望大家不吝赐教。