【深入理解C++11【3】】
1、POD类型
Plain Old Data. Plain 表示 了POD是个普通的类型。C++11将POD划分为两个基本概念的合集:
1)平凡的(trivial)
2)标准布局的(standard layout)
一个平凡的类或结构体应该符合以下定义:
1)拥有平凡的默认构造函数(trivial constructor)和析构函数(trivial destructor)。
平凡 的 默认 构造 函数 就是说 构造 函数“ 什么 都 不干”。 通常 情况下, 不 定义 类 的 构造 函数, 编译器 就会 为我 们 生成 一个 平凡 的 默认 构造 函数。 而 一旦 定义 了 构造 函数, 即使 构造 函数 不 包含 参数, 函数 体 里 也没 有 任何 的 代码, 那么 该 构造 函数 也 不再 是“ 平凡” 的。
2)拥有平凡的拷贝构造函数(trivial copy constructor)和移动构造函数(trivial move constructor)。
3)拥有平凡的拷贝赋值运算符(trivial assignmnt operator)和移动赋值运算符(trivial move operator)。
4)不能包含虚函数以及虚基类。
C++11中可以使用 is_trivial struct来判定一个类型是否是 trivial。
类 模板 is_ trivial 的 成员 value 可以 用于 判断 T 的 类型 是否 是一 个 平凡 的 类型。 除了 类 和 结构 体外, is_ trivial 还可 以对 内置 的 标量 类型 数据( 比如 int、 float 都 属于 平凡 类型) 及 数组 类型( 元素 是 平凡 类型 的 数组 总是 平凡 的) 进行 判断。
template < typename T> struct std:: is_ trivial;
标准布局应该符合以下定义:
1)所有非静态成员有相同的访问权限。
2)在类或结构体继承时,满足以下两种情况之一:
a)派生类中有非静态成员,且只有一个仅包含静态成员的基类。
b)基类有非静态成员,而派生类没有非静态成员。
struct B1 { static int a; }; struct D1 : B1 { int d; }; struct B2 { int a; }; struct D2 : B2 { static int d; }; struct D3 : B2, B1 { static int d; }; struct D4 : B2 { int d; }; struct D5 : B2, D1 { };
D1、 D2 和 D3 都是 标准 布局 的, 而 D4 和 D5 则 不属于 标准 布局 的。
非 静态 成员 只要 同时 出现 在 派生 类 和 基 类 间, 其 即 不属于 标准 布局 的。 而 多重 继承 也会 导致 类型 布局 的 一些 变化, 所以 一旦 非 静态 成员 出现 在 多个 基
3)类中第一个非静态成员的类型与其基类不同。
struct A : B{ B b; }; // 不是标准布局 struct C : B{int a; B b;} // 是标准布局
4)没有虚函数和虚基类。
5)所有 非 静态 数据 成员 均 符合 标准 布局 类型, 其 基 类 也 符合 标准 布局。 这是 一个 递归 的 定义, 没有 什么 好 特别 解释 的。
C++11中可以使用struct模板 is_standard_layout 来判断是否是标准布局类型。
template < typename T> struct std:: is_ standard_ layout;
要判定一个类型是否是 POD,可以使用 is_pod模板:
template < typename T> struct std:: is_ pod;
使用 POD 有 什么 好处 呢? 我们 看 得到 的 大概 有 如下 3 点:
1)字节赋值。memset、memcpy。
2)与C内存布局兼容。
3)保证了静态初始化的安全有效。
2、非受限联合体
C++98中 union不能包含非POD类型。也不允许union拥有静态或引用类型的成员。
在 新的 C++ 11 标准 中, 取消 了 联合体 对于 数据 成员 类型 的 限制。 标准 规定, 任何 非 引用 类型 都可以 成为 联合体 的 数据 成员, 这样 的 联合体 即 所谓 的 非 受限 联合体( Unrestricted Union)。
非 受限 联合体 有一个 非 POD 的 成员, 而 该 非 POD 成员 类型 拥有 有 非 平凡 的 构造 函数, 那么 非 受限 联合体 成员 的 默认 构造 函数 将被 编译器删除。如下:
#include < string> using namespace std; union T { string s; // string 有 非 平凡 的 构造 函数 int n; }; int main() { T t; // 构造 失败, 因为 T 的 构造 函数 被 删除 了 } // 编译 选项: g++ -std= c++ 11 3- 7- 4. cpp
解决 这个 问题 的 办法 是, 由 程序员 自己 为 非 受限 联合体 定义 构造 函数。placement new将会发挥很好的作用。
#include < string> using namespace std; union T { string s; int n; public: // 自定义 构造 函数 和 析 构 函数 T(){ new (&s) string; } ~ T() { s.~ string(); } }; int main() { T t; // 构造 析 构成 功 } // 编译 选项: g++ -std= c++ 11 3- 7- 5. cpp
匿名 非 受限 联合体 可以 运用于 类 的 声明 中。
3、用户自定义字面量 — 后缀标识操作符
struct RGBA{ uint8 r; uint8 g; uint8 b; uint8 a; RGBA( uint8 R, uint8 G, uint8 B, uint8 A = 0): r( R), g( G), b( B), a( A){} }; RGBA operator "" _C( const char* col, size_ t n) { // 一个 长度 为 n 的 字符串 col const char* p = col; const char* end = col + n; const char* r, *g, *b, *a; r = g = b = a = nullptr; for(; p != end; ++ p) { if (*p == 'r') r = p; else if (*p == 'g') g = p; else if (*p == 'b') b = p; else if (*p == 'a') a = p; } if ((r == nullptr) || (g == nullptr) || (b == nullptr)) throw; else if (a == nullptr) return RGBA( atoi( r+ 1), atoi( g+ 1), atoi( b+ 1)); else return RGBA( atoi( r+ 1), atoi( g+ 1), atoi( b+ 1), atoi( b+ 1)); }
除去 字符串 外, 后缀 声明 也可以 作用于 数值, 比如, 用户 可能 使用 60W、 120W 的 表示 方式 来 标识 功率, 用 50kg 来 表示 质量, 用 1200N 来 表示 力 等。
struct Watt{ unsigned int v; }; Watt operator "" _w( unsigned long long v) { return {(unsigned int) v}; } int main() { Watt capacity = 1024_ w; } // 编译 选项: g++ -std= c++ 11 3- 8- 3. cpp
C++11中跟字面量“类型”密切相关的规则有以下四条:
1)如果 字面 量 为 整型 数, 那么 字面 量 操作 符 函数 只可 接受 unsigned long long 或者 const char* 为 其 参数。
2)如果 字面 量 为 浮点 型 数, 则 字面 量 操作 符 函数 只可 接受 long double 或者 const char* 为 参数。
3)如果 字面 量 为 字符串, 则 字面 量 操作 符 函数 函数 只可 接受 const char*, size_ t 为 参数( 已知 长度 的 字符串)。
4)如果 字面 量 为 字符, 则 字面 量 操作 符 函数 只可 接受 一个 char 为 参数。
另外有以下几点要注意:
1)operator"" 与 用户 自定义 后缀 之间 必须 有 空格。
2)后缀 建议 以下 划线 开始。 不宜 使用 非 下划线 后缀 的 用户 自定义 字符串 常量, 否则 会被 编译器 警告。
4、内联名字空间
namespace中的namespace会带来一个问题,如parentspace::childspace,外部使用者调用childspace内的内容的,会比较麻烦,因为需要需要前缀 parentspace::childspace,是否能让外部使用者不需要知道childspace的存在?
C++98 中可以在parentspace中使用 using namespace child; 来解决,但是存在模板特化的问题。C++ 98 标准 不允许 在 不同 的 名字 空 间中 对 模板 进行 特 化。如下:
namespace Jim { namespace Basic { struct Knife{ Knife() { cout << "Knife in Basic." << endl; } }; class CorkScrew{}; } namespace Toolkit{ template< typename T> class SwissArmyKnife{}; } // ... namespace Other{ // ... } // 打开 一些 内部 名字 空间 using namespace Basic; using namespace Toolkit; } // LiLei 决定 对 该 class 进行 特 化 namespace Jim { template<> class SwissArmyKnife< Knife>{}; // 编译 失败 } using namespace Jim; int main() { SwissArmyKnife< Knife> sknife; } // 编译 选项: g++ 3- 9- 2. cpp