在侯捷的《STL源码剖析》里提到trivial和non-trivial及POD类型,相关知识整理如下。
trivial意思是无意义,这个trivial和non-trivial是对类的四种函数来说的:
构造函数(ctor)
复制构造函数(copy)
赋值函数(assignment)
析构函数(dtor)
简单来说,“平凡”意味着这些特殊的成员函数用很朴素的方式完成它们的工作。而”很朴素的方式“这个说法对不同的函数有不同的意义。
对默认构造函数和析构函数来说,“平凡”意味着什么也不做。
对拷贝构造函数和拷贝赋值函数来说,“平凡”意味着只做简单的内存拷贝。
如果至少满足下面3条里的一条:
显式(explict)定义了这四种函数。
类里有非静态非POD的数据成员。
有基类。
那么上面的四种函数是non-trivial函数,比如叫non-trivial ctor、non-trivial copy…,也就是说有意义的函数,里面由一些必要的操作,比如类成员的初始化、释放内存等。
那个POD意思是Plain Old Data,也就是C++的内建类型或传统的C结构体类型。POD类型必然有trivial ctor/dtor/copy/assignment四种函数。
// 整个T是POD类型
class T
{
// 没有显式定义ctor/dtor/copy/assignemt所以都是trivial
int a; // POD类型
};
// 整个T1是非POD类型
class T1
{
T1() // 显式定义了构造函数,所以是non-trivial ctor
{
}
// 没有显式定义dtor/copy/assignemt所以都是trivial
int a; // POD类型
std::string b; // 非POD类型
};
对POD类型的一些补充:
POD 是英文中 Plain Old Data 的缩写,翻译过来就是 普通的旧数据 。POD 在 C++ 中是非常重要的一个概念,通常用于说明一个类型的属性,尤其是用户自定义类型的属性。
POD 属性在 C++11 中往往又是构建其他 C++ 概念的基础,事实上,在 C++11 标准中,POD 出现的概率相当高。因此学习 C++,尤其是在 C++11 中,了解 POD 的概念是非常必要的。
Plain :表示是个普通的类型
Old :体现了其与 C 的兼容性,支持标准 C 函数
在 C++11 中将 POD 划分为两个基本概念的合集,即∶平凡的(trivial) 和标准布局的(standard layout )。
一个平凡的类或者结构体应该符合以下几点要求:
1、拥有平凡的默认构造函数(trivial constructor)和析构函数(trivial destructor)。
平凡的默认构造函数就是说构造函数什么都不干。
通常情况下,不定义类的构造函数,编译器就会为我们生成一个平凡的默认构造函数。
// 使用默认的构造函数
class Test {};
一旦定义了构造函数,即使构造函数不包含参数,函数体里也没有任何的代码,那么该构造函数也不再是"平凡"的。
class Test1
{
Test1(); // 程序猿定义的构造函数, 非默认构造
};
关于析构函数也和上面列举的构造函数类似,一旦被定义就不平凡了。但是这也并非无药可救,使用=default 关键字可以显式地声明默认的构造函数,从而使得类型恢复“平凡化”。
2、拥有平凡的拷贝构造函数(trivial copy constructor)和移动构造函数(trivial move constructor)。
平凡的拷贝构造函数基本上等同于使用 memcpy 进行类型的构造。
同平凡的默认构造函数一样,不声明拷贝构造函数的话,编译器会帮程序员自动地生成。
可以显式地使用 = default 声明默认拷贝构造函数。
而平凡移动构造函数跟平凡的拷贝构造函数类似,只不过是用于移动语义。
3、拥有平凡的拷贝赋值运算符(trivial assignment operator)和移动赋值运算符(trivial move operator)。
这基本上与平凡的拷贝构造函数和平凡的移动构造函数类似。
4、不包含虚函数以及虚基类。
类中使用 virtual 关键字修饰的函数 叫做虚函数
class Base
{
public:
Base() {}
virtual void print() {}
};
虚基类是在创建子类的时候在继承的基类前加 virtual 关键字修饰
语法:
class 派生类名:virtual 继承方式 基类名
示例代码:
class Base
{
public:
Base() {}
};
// 子类Child,虚基类:Base
class Child : virtual public Base
{
Child() {}
};
那这有什么用处呢?
如果这个类是trivial ctor/dtor/copy/assignment函数,我们对这个类进行构造、析构、拷贝和赋值的时候可以采用最有效率的方法,不调用无所事事的那些ctor/dtor等,而直接采用内存操作如malloc()、memcpy()等提高性能,这也是SGI STL内部干的事情。
比如STL的copy算法最基本的想法是这样的:
// 非POD重载指针数值
template << / span > class T > void copy(T *source, T *destination, int n, __false_type)
{
// 省略异常处理
for (; n > 0; n--, source++, destination++)
{
// 调用source的拷贝构造函数
constructor(source, *destination);
}
}
// POD重载指针数值
template << / span > class T > void copy(T *source, T *destination, int n, __false_type)
{
// 省略异常处理
memmove(source, destination, n);
}
当然实际的copy比这个复杂多了,有非常多的特化等,这个只是其中一方面而已。