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

上一篇:《深入理解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{    // 非静态成员权限不同,所以不标准,如果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类型的好处:

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
原子类型和原子操作是并发编程中的重要概念。在多线程编程中,当多个线程同时操作同一个共享变量时,可能会出现数据竞争的问题,导致程序的结果不可预测。为了解决这个问题,C11引入了原子类型和原子操作。 原子类型是一种特殊的数据类型,它的操作都是原子的,即不会被其他线程的操作干扰。C11提供了几种原子类型,包括原子整型、原子指针和原子布尔型等。原子类型可以在多线程环境下安全地进行读写操作,保证数据的一致性。 原子操作是对原子类型进行的操作,包括赋值、递增、递减等。这些操作都是原子的,不会被其他线程的操作干扰。原子操作通过一些特殊的语法和函数来实现,如原子操作的语法是“Atomics”开头的函数。例如,使用原子操作可以通过atomic_store函数将一个值存储到原子类型的变量中,保证线程安全。 使用原子类型和原子操作可以简化并发编程的复杂度,避免数据竞争。原子类型和原子操作提供了一种高效的并发编程模型,在多线程编程中具有重要的应用价值。 在使用原子类型和原子操作时,需要注意一些问题。首先,原子操作虽然保证了操作的原子性,但并不能完全解决所有的并发问题。其次,原子操作的性能可能不如普通操作,因为原子操作需要保证线程安全,可能需要加锁等额外开销。 总之,原子类型和原子操作是C11提供的一种并发编程的解决方案。通过使用原子类型和原子操作,可以有效解决多线程编程中的数据竞争问题,提高程序的并发性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值