《深入理解C++11》笔记–POD类型

上一篇:《深入理解C++11》笔记–列表初始化
本编继续介绍第三章的内容:POD类型,plain old data。Plain代表数据是普通类型,old代表能与C兼容支持memcpy、memset等函数。POD分为两个部分,trival(平凡的)和(s’tan’dard layout)标准布局的,必须同时满足才是POD类型。

平凡的类或结构体必须满足以下的条件:
- 平凡的默认构造函数和析构函数。只要是自己定义了函数,即使实现为空,也不再平凡。所以就是说不能自定义默认构造函数和析构函数。
- 平凡的拷贝构造函数和移动构造函数。
- 平凡的赋值构造函数。
- 不能包含虚函数和虚基类。

另外,可以用std::is_trival来判断是不是一个平凡类型,std::is_trival<T>::value,例如:

class TrivalA {
};
class TrivalB {
public :
    int a;
};
class TrivalC {
    TrivalC() {}                   // 有默认构造函数,不平凡
};
class TrivalD {
    TrivalD(const TrivalD& a) {}   // 有赋值构造函数,不平凡
};
class TrivalE {
    TrivalE(TrivalE&& a) {}        // 有移动构造函数,不平凡
};
class TrivalF {
    TrivalF& operator=(const TrivalF& a) {}   // 有赋值构造函数,不平凡
};
class TrivalG {
    virtual void func() = 0;                  // 有虚函数,不平凡
};
class TrivalH: virtual public TrivalA {               // 有虚基类,不平凡
};

int main(int argc, char **argv)
{
    std::cout << std::is_trivial<TrivalA>::value << std::endl;     // 1
    std::cout << std::is_trivial<TrivalB>::value << std::endl;     // 1
    std::cout << std::is_trivial<TrivalC>::value << std::endl;     // 从这里开始都是0
    std::cout << std::is_trivial<TrivalD>::value << std::endl;
    std::cout << std::is_trivial<TrivalE>::value << std::endl;
    std::cout << std::is_trivial<TrivalF>::value << std::endl;
    std::cout << std::is_trivial<TrivalG>::value << std::endl;
    std::cout << std::is_trivial<TrivalH>::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{    // 非静态成员权限不同,所以不标准,如果ab有一个是静态,那么是标准的
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类型的好处:

猜你喜欢

转载自blog.csdn.net/wizardtoh/article/details/80767740