3. C++ POD类型

POD全称Plain Old Data,通常用于说明1个类型的属性。通俗的讲,一个类或结构体通过二进制拷贝后还能保持其数据不变,那么它就是一个POD类型。

C++11将POD划分为2个基本概念的合集,即平凡的(trivual)和标准布局的(standard layant)

1. 平凡的定义

  • 有平凡的默认构造函数和析构函数。平凡的默认构造函数就是说构造函数“什么都不干”。通常情况下,不定义类的构造函数,编译器就会为我们生成一个平凡的默认构造函数。而一旦定义了构造函数,即使构造函数不包含参数,函数体也没有任何代码,那么该构造函数也不再是“平凡”的。
  • 有平凡的拷贝构造函数和移动构造函数。不声明拷贝构造函数的话,编译器会自动生成,可以使用=default声明默认拷贝构造函数。
  • 有平凡的拷贝赋值运算符和移动赋值运算符。
  • 不能包含虚函数以及虚基类。
class A { A(){} };
class B { B(B&){} };
class C { C(C&&){} };
class D { D operator=(D&){} };
class E { E operator=(E&&){} };
class F { ~F(){} };
class G { virtual void foo() = 0; };
class H : G {};
class I {};

int main()
{
    std::cout << std::is_trivial<A>::value << std::endl;  // 有不平凡的构造函数
    std::cout << std::is_trivial<B>::value << std::endl;  // 有不平凡的拷贝构造函数
    std::cout << std::is_trivial<C>::value << std::endl;  // 有不平凡的拷贝赋值运算符
    std::cout << std::is_trivial<D>::value << std::endl;  // 有不平凡的拷贝赋值运算符
    std::cout << std::is_trivial<E>::value << std::endl;  // 有不平凡的移动赋值运算符
    std::cout << std::is_trivial<F>::value << std::endl;  // 有不平凡的析构函数
    std::cout << std::is_trivial<G>::value << std::endl;  // 有虚函数
    std::cout << std::is_trivial<H>::value << std::endl;  // 有虚基类

    std::cout << std::is_trivial<I>::value << std::endl;  // 平凡的类
    
    return 0;
}

 

2. 标准布局的定义

  • 所有非静态成员有相同的访问权限
  • 继承树中最多只能有一个类有非静态数据成员
  • 子类的第一个非静态成员不可以是基类类型
  • 没有虚函数
  • 没有虚基类
  • 所有非静态成员都符合标准布局类型
class A
{
private:
    int a;
public:
    int b;
};

class B1
{
    static int x1;
};

class B2
{
    int x2;
};

class B : B1, B2
{
    int x;
};

class C1 {};
class C : C1
{
    C1 c;
};

class D { virtual void foo() = 0; };
class E : D {};
class F { A x; };

int main()
{
    std::cout << std::is_standard_layout<A>::value << std::endl;  // 违反定义1。成员a和b具有不同的访问权限
    std::cout << std::is_standard_layout<B>::value << std::endl;  // 违反定义2。继承树有两个(含)以上的类有非静态成员
    std::cout << std::is_standard_layout<C>::value << std::endl;  // 违反定义3。第一个非静态成员是基类类型
    std::cout << std::is_standard_layout<D>::value << std::endl;  // 违反定义4。有虚函数
    std::cout << std::is_standard_layout<E>::value << std::endl;  // 违反定义5。有虚基类
    std::cout << std::is_standard_layout<F>::value << std::endl;  // 违反定义6。非静态成员x不符合标准布局类型

    return 0;
}

 

3. POD

当一个数据类型满足了”平凡的定义“和”标准布局“,我们则认为它是一个POD数据。可以通过std::is_pod来判断一个类型是否为POD类型。一个POD类型是可以进行二进制拷贝的。

class A
{
public:
    int x;
    double y;
};

int main()
{
    if (std::is_pod<A>::value)
    {
        std::cout << "before" << std::endl;
        A a;
        a.x = 8;
        a.y = 10.5;
        std::cout << a.x << std::endl;
        std::cout << a.y << std::endl;

        size_t size = sizeof(a);
        char *p = new char[size];
        memcpy(p, &a, size);
        A *pA = (A*)p;

        std::cout << "after" << std::endl;
        std::cout << pA->x << std::endl;
        std::cout << pA->y << std::endl;

        delete p;
    }

    return 0;
}

运行结果如下:

 4. POD的作用

  • 字节赋值,代码中我们可以安全地使用memset和memcpy对POD类型进行初始化和拷贝等操作。
  • 提供对C内存布局兼容。C++程序可以与C函数进行相互操作,因为POD类型的数据在C与C++间的操作总是安全的。
  • 保证了静态初始化的安全有效。静态初始化在很多时候能够提供程序的性能,而POD类型的对象初始化往往更加简单。
posted on 2019-04-30 09:48  anlyse  阅读(458)  评论(0)    收藏  举报