什么是POD类型

POD类型

POD(Plain Old Data)是一种在C++中描述数据结构的术语。POD类型是一种特殊的类别,具有简单的数据成员,没有用户定义的构造函数、析构函数、拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符。POD类型通常是用于与C语言代码进行交互或在低级别的内存操作中

C++11及更高版本引入了更灵活的术语,称为**TrivialStandard Layout**,这些术语更精确地描述了类型的特性。

Trivial类型(平凡类型)

一个平凡的类或者结构体应该符合以下几点要求:

1.拥有平凡的默认构造函数(trivial constructor)和析构函数(trivial destructor)。

平凡的默认构造函数就是说构造函数什么都不干。

通常情况下,不定义类的构造函数,编译器就会为我们生成一个平凡的默认构造函数。

// 使用默认的构造函数
class Test {};

一旦定义了构造函数,即使构造函数不包含参数,函数体里也没有任何的代码,那么该构造函数也不再是"平凡"的。

关于析构函数也和上面列举的构造函数类似,一旦被定义就不平凡了。但是这也并非无药可救,使用=default关键字可以显式地声明默认的构造函数,从而使得类型恢复 “平凡化”

2.拥有平凡的拷贝构造函数(trivial copy constructor)和移动构造函数(trivial move constructor)。
  • 平凡的拷贝构造函数基本上等同于使用memcpy 进行类型的构造。
  • 同平凡的默认构造函数一样,不声明拷贝构造函数的话,编译器会帮程序员自动地生成
  • 可以显式地使用=default 声明默认拷贝构造函数。
  • 而平凡移动构造函数跟平凡的拷贝构造函数类似,只不过是用于移动语义。
3.拥有平凡的拷贝赋值运算符(trivial assignment operator)和移动赋值运算符(trivial move operator)。

这基本上与平凡的拷贝构造函数和平凡的移动构造运算符类似。

4.不包含虚函数以及是虚基类的父类。
#include <iostream>
#include <type_traits>
using namespace std;

class MyPODType {
public:
    int x;
    float y;
};

class Son1 : virtual public MyPODType {

};

class Son2 : virtual public MyPODType {

};

class Sun : public Son1, public Son2 {

};

int main() {
    static_assert(std::is_trivial<MyPODType>::value, "MyPODType should be trivial");
    static_assert(std::is_standard_layout<MyPODType>::value, "MyPODType should be standard layout");
}

这里只有MyPODType进行判断的时候不会报错

Standard Layout (标准布局类型)

标准布局类型主要主要指的是类或者结构体的结构或者组合方式。

标准布局类型的类应该符合以下五点定义,最重要的为前两条:

1.所有非静态成员有相同 的访问权限(public,private,protected)。

类成员拥有不同的访问权限(非标准布局类型)

class Base
{
public:
    Base() {}
    int a;
protected:
    int b;
private:
    int c;
};
2.在类或者结构体继承时,满足以下两种情况之一∶

派生类中有非静态成员,基类中包含静态成员(或基类没有变量)。
基类有非静态成员,而派生类没有非静态成员。

struct Base { static int a;};
struct Child: public Base{ int b;};          // ok
struct Base1 { int a;};
struct Child1: public Base1{ static int c;}; // ok
struct Child2:public Base, public Base1 { static int d;); // ok
struct Child3:public Base1{ int d;};         // error
struct Child4:public Base1, public Child     // error
{
    static int num;
};

通过上述例子得到的结论:

  1. 非静态成员只要同时出现在派生类和基类间,即不属于标准布局。
  2. 对于多重继承,一旦非静态成员出现在多个基类中,即使派生类中没有非静态成员变量,派生类也不属于标准布局。
3.子类中第一个非静态成员的类型与其基类不同。

此处基于G++编译器讲解,如果使用VS的编译器和G++编译器得到的结果是不一样的。

struct Parent{};
struct Child : public Parent
{
    Parent p;	// 子类的第一个非静态成员
    int foo;
};

上面的例子中Child不是一个标准布局类型,因为它的第一个非静态成员变量p和父类的类型相同,改成下面这样子类就变成了一个标准布局类型

struct Parent{};
struct Child1 : public Parent
{
    int foo;   // 子类的第一个非静态成员
    Parent p;	
};

这条规则对于我们来说是比较特别的,这样规定的目的主要是是节约内存,提高数据的读取效率。对于上面的两个子类Child和Child1来说它们的内存结构是不一样的,在基类没有成员的情况下:

  • C++标准允许标准布局类型(Child1)派生类的第一个成员foo与基类共享地址,此时基类并没有占据任何的实际空间(可以节省一点数据)
  • 对于子类Child而言,如果子类的第一个成员仍然是基类类型,C++标准要求类型相同的对象它们的地址必须不同(基类地址不能和子类中的变量 p 类型相同),此时需要分配额外的地址空间将二者的地址错开

在这里插入图片描述

4.没有虚函数和虚基类。
5.所有非静态数据成员均符合标准布局类型,其基类也符合标准布局,这是一个递归的定义。

对 POD 类型的判断

在现代C++中,更推荐使用 std::is_trivial, std::is_standard_layout 这样的类型特性检查工具,而不是手动检查是否为POD类型。这些工具更准确,同时也更易读。例如:

#include <type_traits>

class MyPODType {
public:
    int x;
    float y;
};

static_assert(std::is_trivial<MyPODType>::value, "MyPODType should be trivial");
static_assert(std::is_standard_layout<MyPODType>::value, "MyPODType should be standard layout");

这里,static_assert 将在编译时检查 MyPODType 是否为 trivial 和 standard layout 类型。

单独使用的话, 返回值是布尔类型

#include <iostream>
#include <type_traits>
using namespace std;

class A {};
class B { B() {} };
class C : B {};
class D { virtual void fn() {} };
class E : virtual public A { };

int main() 
{
    cout << std::boolalpha;
    cout << "is_trivial:" << std::endl;
    cout << "int: " << is_trivial<int>::value << endl;
    cout << "A: " << is_trivial<A>::value << endl;
    cout << "B: " << is_trivial<B>::value << endl;
    cout << "C: " << is_trivial<C>::value << endl;
    cout << "D: " << is_trivial<D>::value << endl;
    cout << "E: " << is_trivial<E>::value << endl;
    return 0;
}

输出的结果为:

is_trivial:
int: true
A: true
B: false
C: false
D: false
E: false

int :内置标准数据类型,属于 trivial 类型
A :拥有默认的构造和析构函数,属于 trivial 类型
B :自定义了构造函数,因此不属于 trivial 类型
C :基类中自定义了构造函数,因此不属于 trivial 类型
D :类成员函数中有虚函数,因此不属于 trivial 类型
E :继承关系中有虚基类,因此不属于 trivial 类型

总结

事实上,我们使用的很多内置类型默认都是 POD的。POD 最为复杂的地方还是在类或者结构体的判断。不过上面也给大家介绍了判断的方法,相信大家对 POD已经有所理解。那么,使用POD有什么好处呢?

  1. 字节赋值,代码中我们可以安全地使用memset 和 memcpy 对 POD类型进行初始化和拷贝等操作。
  2. 提供对C内存布局兼容。C++程序可以与C 函数进行相互操作,因为POD类型的数据在C与C++ 间的操作总是安全的。
  3. *保证了静态初始化的安全有效。静态初始化在很多时候能够提高程序的性能,而POD类型的对象初始化往往更加简单。
  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值