上一篇:《深入理解C++11》笔记–列表初始化
本编继续介绍第三章的内容:POD类型,plain old data。Plain代表数据是普通类型,old代表能与C兼容支持memcpy、memset等函数。POD分为两个部分,trivial(平凡的)和(s’tan’dard layout)标准布局的,必须同时满足才是POD类型。
平凡的类或结构体必须满足以下的条件:
- 平凡的默认构造函数和析构函数。只要是自己定义了函数,即使实现为空,也不再平凡。所以就是说不能自定义默认构造函数和析构函数。
- 平凡的拷贝构造函数和移动构造函数。
- 平凡的赋值构造函数。
- 不能包含虚函数和虚基类。
另外,可以用std::is_trivial来判断是不是一个平凡类型,std::is_trivial<T>::value
,例如:
class TrivialA {
};
class TrivialB {
public :
int a;
};
class TrivialC {
TrivialC() {} // 有默认构造函数,不平凡
};
class TrivialD {
TrivialD(const TrivialD& a) {} // 有赋值构造函数,不平凡
};
class TrivialE {
TrivialE(TrivalE&& a) {} // 有移动构造函数,不平凡
};
class TrivialF {
TrivialF& operator=(const TrivialF& a) {} // 有赋值构造函数,不平凡
};
class TrivialG {
virtual void func() = 0; // 有虚函数,不平凡
};
class TrivialH: virtual public TrivialA { // 有虚基类,不平凡
};
int main(int argc, char **argv)
{
std::cout << std::is_trivial<TrivialA>::value << std::endl; // 1
std::cout << std::is_trivial<TrivialB>::value << std::endl; // 1
std::cout << std::is_trivial<TrivialC>::value << std::endl; // 从这里开始都是0
std::cout << std::is_trivial<TrivialD>::value << std::endl;
std::cout << std::is_trivial<TrivialE>::value << std::endl;
std::cout << std::is_trivial<TrivialF>::value << std::endl;
std::cout << std::is_trivial<TrivialG>::value << std::endl;
std::cout << std::is_trivial<TrivialH>::value << std::endl;
return 0;
}
标准布局的类或结构体必须满足以下的条件:
- 所有非静态成员有相同的访问权限。
- 在类或结构体继承时满足以下两个条件之一:
1、派生类中有非静态成员,且只有仅包含静态成员的基类。
2、基类有非静态成员,而派生类没有非静态成员。
其实就是派生类和基类中不允许同时出现非静态成员,因为同时有非静态成员就无法进行memcpy - 类中第一个非静态成员的类型与基类不同。
C++标准允许,在基类没有成员时,派生类第一个成员与基类共享地址。但是当派生类中第一个数据成员类型为基类类型时,有趣的问题就来了。首先,这时派生类的内存布局包括基类部分的内存布局,同时自己又添加了另外一个基类类型的变量,如果编译器优化实现第一个成员和基类部分共享地址,那么就违背了C++标准的另一个要求,同类型的不同对象地址必须不同。 - 没有虚函数和虚基类。
- 所有非静态成员均符合标准布局,其基类也符合标准布局。
另外,可以用std::is_standard_layout来判断是不是一个标准布局,std::is_standard_layout<T>::value
,例如:
class StdLayoutA {
};
class StdLayoutB {
public :
int a;
int b;
};
class StdLayoutC : public StdLayoutA {
public:
int a;
int b;
void fun() {}
};
class StdLayoutD : public StdLayoutA {
public:
int a;
StdLayoutA sla;
};
class StdLayoutE : public StdLayoutA , public StdLayoutC {
};
class StdLayoutF {
public:
static int a;
};
class StdLayoutG : public StdLayoutF {
public:
int a;
};
class StdLayoutH: public StdLayoutA { // 第一个非静态成员是基类,所以不标准,如果sla的位置和b交换,那么是标准的
public:
StdLayoutA sla;
int b;
};
class StdLayoutI : public StdLayoutB { // 基类和派生类都有非静态变量,所以不标准,如果基类或者派生类中只有静态变量,那么是标准的
public:
int a;
};
class StdLayoutJ: public StdLayoutI { // 基类不标准,所以不标准,如果StdLayoutI标准,那么是标准的
};
class StdLayoutK{ // 非静态成员权限不同,所以不标准,如果a或b有一个是静态,那么是标准的
public:
int a;
private:
int b;
};
int main(int argc, char **argv)
{
std::cout << std::is_standard_layout<StdLayoutA>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutB>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutC>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutD>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutE>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutF>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutG>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutH>::value << std::endl; // 以上都是1,从这里开始都是0
std::cout << std::is_standard_layout<StdLayoutI>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutJ>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutK>::value << std::endl;
return 0;
}
针对POD类型也有模板类:std::is_pod<T>::value
。了解了POD类型的要求,再来解释POD类型的好处:
- 字节赋值,可以使用memset和memcpy。
- 兼容C语言的内存布局
- 保证静态初始化安全有效
下一篇:《深入理解C++11》笔记–非受限联合体