【深入理解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
在 C++ 11 中, 标准 引入 了 一个 新 特性, 叫做“ 内联的名字空间”。 通过 关键字“ inline namespace” 就可以 声明 一个 内联 的 名字 空间。 内联 的 名字 空间 允许 程序员 在 父名字空间 定义 或 特 化子名字空间 的 模板。
namespace Jim { inline namespace Basic { struct Knife{ Knife() { cout << "Knife in Basic." << endl; } }; class CorkScrew{}; } inline namespace Toolkit{ template< typename T> class SwissArmyKnife{}; } // ... namespace Other{ Knife b; // Knife in Basic struct Knife{ Knife() { cout << "Knife in Other" << endl;} }; Knife c; // Knife in Other Basic:: Knife k; // Knife in Basic } // 打开 一些 内部 名字 空间 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
上面对 inline namespace 的用法是一种误用,上述用法跟将 childspace中的内容放到 parentspace没有区别。inline namespace正确的用法是用于版本管理。如:
namespace Jim { #if __cplusplus == 201103L inline #endif namespace cpp11{ struct Knife{ Knife() { cout << "Knife in c++ 11." << endl; } }; // ... } #if __cplusplus < 201103L inline #endif namespace oldcpp{ struct Knife{ Knife() { cout << "Knife in old c++." << endl; } }; // ... } } using namespace Jim; int main() { Knife a; // Knife in c++ 11. (默认 版本) cpp11:: Knife b; // Knife in c++ 11. (强制 使用 cpp11 版本) oldcpp:: Knife c; // Knife in old c++. (强制 使用 oldcpp11 版本) } // 编译 选项: g++ -std= c++ 11 3- 9- 4. cpp
所谓“ 参数 关联 名称 查找”, 即 ADL( Argument- Dependent name Lookup)。 这个 特性 允许 编译器 在 名字 空 间内 找 不到 函数 名称 的 时候, 在 参数 的 名字 空间 内 查找 函数 名字。
namespace ns_ adl{ struct A{}; void ADLFunc( A a){} // ADLFunc 定义 在 namespace ns_ adl 中 } int main() { ns_ adl:: A a; ADLFunc( a); // ADLFunc 无需 声明 名字 空间 }
5、模板的别名。
C++98中只有typedef可以定义类型的别名,C++11中using也可以定义类型的别名。
#include < iostream> #include < type_ traits> using namespace std; using uint = unsigned int; typedef unsigned int UINT; using sint = int; int main() { cout << is_ same< uint, UINT>:: value << endl; // 1 } // 编译 选项: g++ -std= c++ 11 3- 10- 1. cpp
不止于此,C++11中using 还可以结合模板使用。
template< typename T> using MapString = std:: map< T, char*>; MapString< int> numberedString;
6、SFINEA,Substitution failure is not an error.
对 重载 的 模板 的 参数 进行 展开 的 时候, 如果 展开 导致 了 一些 类型 不匹配, 编译器 并不 会 报错。如下:
struct Test { typedef int foo; }; template < typename T> void f( typename T:: foo) {} // 第一个 模板 定义 - #1 template < typename T> void f( T) {} // 第二个 模板 定义 - #2 int main() { f< Test>( 10); // 调用# 1. f< int>( 10); // 调用# 2. 由于 SFINEA, 虽然 不存在 类型 int:: foo, 也不 会 发生 编译 错误 } // 编译 选项: g++ 2- 14- 1. cpp
7、右尖括号的改进
在 C++ 98 中, 有 一条 需要 程序员 规避 的 规则: 如果 在 实例 化 模板 的 时候 出现 了 连续 的 两个 右 尖 括号>, 那么 它们 之间 需要 一个 空格 来 进行 分隔, 以 避免 发生 编译 时 的 错误。
template < int i> class X{}; template < class T> class Y{}; Y< X< 1> > x1; // 编译 成功 Y< X< 2>> x2; // 编译 失败 // 编译 选项: g++ -c 4- 1- 1. cpp
上面代码,在 x2 的 定义 中, 编译器 会把>> 优先 解析 为 右移 符号。除去上面这个例子,在使用 *_cast 时也会有同样的问题。如:
const vector< int> v = static_cast< vector< int>>( v); //无法 通过 编译
C++11中,这种限制被取消了。
8、auto
int main() { double foo(); auto x = 1; // x 的 类型 为 int auto y = foo(); // y 的 类型 为 double struct m { int i; } str; auto str1 = str; // str1 的 类型 是 struct m auto z; // 无法 推导, 无法 通过 编译 z = x; } // 编译 选项: g++ -std= c++ 11 4- 2- 2. cpp
在类型自定义类型转换情形中,auto 能够帮上大忙:
class PI { public: double operator* (float v) { return (double) val * v; // 这里 精度 被 扩展 了 } const float val = 3. 1415927f; }; int main() { float radius = 1. 7e10; PI pi; auto circumference = 2 * (pi * radius); } // 编译 选项: g++ -std= c++ 11 4- 2- 5. cpp
auto的另一特性是自适应:
1)假设上述代码 PI 的 作者 改动 了 PI 的 定义, 比如 将 operator* 返回 值 变为 long double, 此时, main 函数 并不 需要 修改, 因为 auto 会“ 自 适应” 新的 类型。
2)对于 不同 的 平 台上 的 代码 维护, auto 也会 带来 一些“ 泛 型” 的 好处。 这里 我们 以 strlen 函数 为例, 在 32 位 的 编译 环境 下, strlen 返回 的 为 一个 4 字节 的 整型, 而在 64 位 的 编译 环境 下, strlen 会 返回 一个 8 字节 的 整型。
3)当 auto 应用于 模板 的 定义 中, 其“ 自 适应” 性 会 得到 更加 充分 的 体现。如下:
template< typename T1, typename T2> double Sum( T1 & t1, T2 & t2) { auto s = t1 + t2; // s 的 类型 会在 模板 实例 化 时 被 推导 出来 return s; } int main() { int a = 3; long b = 5; float c = 1. 0f, d = 2. 3f; auto e = Sum< int ,long>( a, b); // s 的 类型 被 推导 为 long auto f = Sum< float, float>( c, d); // s 的 类型 被 推导 为 float } // 编译 选项: g++ -std= c++ 11 4- 2- 7. cpp
auto 可以提升性能:
#define Max1( a, b) ((a) > (b)) ? (a) : (b) #define Max2( a, b) ({ \ auto _a = (a); \ auto _b = (b); \ (_a > _b) ? _a: _b; }) int main() { int m1 = Max1( 1* 2* 3* 4, 5+ 6+ 7+ 8); int m2 = Max2( 1* 2* 3* 4, 5+ 6+ 7+ 8); } // 编译 选项: g++ -std= c++ 11 4- 2- 8. cpp
前者 采用 传统 的 三元 运算符 表达式, 这 可能 会 带来 一定 的 性能 问题。 因为 a 或者 b 在 三元 运算符 中 都 出现 了 两次,
9、auto的使用细则
实际上 对于 a、 c、 d 三个 变量 而言, 声明 其为 auto* 或 auto 并没有 区别。 而 如果 要 使得 auto 声明 的 变量 是 另一个 变量 的 引用, 则 必须 使用 auto&。
int x; int * y = &x; double foo(); int & bar(); auto * a = &x; // int* auto & b = x; // int& auto c = y; // int* auto * d = y; // int* auto * e = &foo(); // 编译 失败, 指针 不能 指向 一个 临时 变量 auto & f = foo(); // 编译 失败, nonconst 的 左 值 引用 不能 和 一个 临时 变量 绑 定 auto g = bar(); // int auto & h = bar(); // int& // 编译 选项: g++ -std= c++ 11 4- 2- 9. cpp
声明 为 auto 的 变量 并不能 从其 初始化 表达式 中“ 带走” cv 限制 符。
double foo(); float * bar(); const auto a = foo(); // a: const double const auto & b = foo(); // b: const double& volatile auto * c = bar(); // c: volatile float* auto d = a; // d: double auto & e = a; // e: const double & auto f = c; // f: float * volatile auto & g = c; // g: volatile float * & // 编译 选项: g++ -std= c++ 11 4- 2- 10. cpp
上述代码中,通过 auto 声明 的 变量 d、 f 却 无法 带走 a 和 f 的 常量 性 或者 易 失 性。 这里 的 例外 还是 引用, 可以 看出, 声明 为 引用 的 变量 e、 g 都 保持 了 其 引用 的 对象 相同 的 属性(
用 auto 来 声明 多个 变量 类型 时, 只有 第一个 变量 用于 auto 的 类型 推导。注意 const auto *:
auto x = 1, y = 2; // x 和 y 的 类型 均为 int // m 是一 个 指向 const int 类型 变量 的 指针, n 是 一个 int 类型 的 变量 const auto* m = &x, n = 1; auto i = 1, j = 3. 14f; // 编译 失败 auto o = 1, &p = o, *q = &p; // 从左 向右 推导 // 编译 选项: g++ -std= c++ 11 4- 2- 11. cpp -c
C++ 11 新 引入 的 初始化 列表, 以及 new, 都可以 使用 auto 关键字
#include < initializer_ list> auto x = 1; auto x1( 1); auto y {1}; // 使用 初始化 列表 的 auto auto z = new auto( 1); // 可以 用于 new // 编译 选项: g++ -std= c++ 11 -c 4- 2- 12. cpp
禁止使用 auto 的情形:
1)auto不能是函数形参。
2)非静态成员变量的类型不能是auto。
3)不能声明 auto 数组。
4)不能在模板参数中使用 auto。
#include < vector> using namespace std; void fun( auto x = 1){} // 1: auto 函数 参数, 无法 通过 编译 struct str{ auto var = 10; // 2: auto 非 静态 成员 变量, 无法 通过 编译 }; int main() { char x[ 3]; auto y = x; auto z[ 3] = x; // 3: auto 数组, 无法 通过 编译 // 4: auto 模板 参数( 实例 化 时), 无法 通过 编译 vector< auto> v = {1}; } // 编译 选项: g++ -std= c++ 11 4- 2- 13. cpp
10、typeid 与 decltype
C++ 98 对 动态 类型 支持 就是 C++ 中的 运行时 类型 识别( RTTI)。
RTTI 的 机制 是 为 每个 类型 产生 一个 type_info 类型的数据, 程序员 可以 在 程序 中 使用 typeid 随时 查询 一个 变量 的 类型, typeid 就会 返回 变量 相应 的 type_info 数据。 而 type_info 的 name 成员 函数 可以 返回 类型 的 名字。
而在 C++ 11 中, 又 增加 了 hash_code 这个成员函数, 返回该类型唯一的哈希值, 以供 程序员 对 变量 的 类型 随时 进行 比较。
#include < iostream> #include < typeinfo> using namespace std; class White{}; class Black{}; int main() { White a; Black b; cout << typeid( a). name() << endl; // 5White cout << typeid( b). name() << endl; // 5Black White c; bool a_ b_ sametype = (typeid( a). hash_ code() == typeid( b). hash_ code()); bool a_ c_ sametype = (typeid( a). hash_ code() == typeid( c). hash_ code()); cout << "Same type? " << endl; cout << "A and B? " << (int) a_ b_ sametype << endl; // 0 cout << "A and C? " << (int) a_ c_ sametype << endl; // 1 } // 编译 选项: g++ -std= c++ 11 4- 3- 1. cpp
上面代码中,5 这样 的 前缀 是 g++ 这类 编译器 输出 的 名字, 其他 编译器 可能 会 打印 出 其他 的 名字, 这个 标准 并没有 明确 规定),
由于 RTTI 会 带来 一些 运行时 的 开销, 所以 一些 编译器 会 让 用户 选择 性地 关闭 该 特性( 比如 XL C/ C++ 编译器 的- qnortti, GCC 的 选项- fno- rttion, 或者 微软 编译器 选项/ GR-)。
decltype 总是 以 一个 普通 的 表达式 为 参数, 返回 该 表达式 的 类型。decltype的demo:
int main() { int i; decltype( i) j = 0; cout << typeid( j). name() << endl; // 打 印出" i", g++ 表示 int float a; double b; decltype( a + b) c; cout << typeid( c). name() << endl; // 打 印出" d", g++ 表示 double } // 编译 选项: g++ -std= c++ 11 4- 3- 2. cpp
11、decltype的应用
decltype 与 typedef/using 的全用:
using size_ t = decltype( sizeof( 0)); using ptrdiff_ t = decltype(( int*) 0 - (int*) 0); using nullptr_ t = decltype( nullptr);
decltype 可以用于获取匿名类型的类型。这是C++98所做不到的。
enum class{ K1, K2, K3} anon_ e; // 匿名 的 强 类型 枚举 union { decltype( anon_ e) key; char* name; }anon_ u; // 匿名 的 union 联合体 struct { int d; decltype( anon_ u) id; }anon_ s[ 100]; // 匿名 的 struct 数组 int main() { decltype( anon_ s) as; as[ 0]. id. key = decltype( anon_ e):: K1; // 引用 匿名 强 类型 枚举 中的 值 } // 编译 选项: g++ -std= c++ 11 4- 3- 4. cpp
与模板特化结合:
template< typename T1, typename T2> void Sum( T1 & t1, T2 & t2, decltype( t1 + t2) & s) { s = t1 + t2; } void Sum( int a[], int b[], int c[]) { // 数组 版本 } int main() { int a[ 5], b[ 5], c[ 5]; Sum( a, b, c); // 选择 数组 版本 int d, e, f; Sum( d, e, f); // 选择 模板 的 实例 化 版本 } // 编译 选项: g++ -std= c++ 11 4- 3- 6. cpp
decltype 只能 接受 表达式 做 参数, 像 函数 名 做 参数 的 表达式 decltype( hash) 是 无法 通过 编译 的。
int hash( char*); map< char*, decltype( hash)> dict_ key; // 无法 通过 编译 map< char*, decltype( hash( nullptr))> dict_ key1;
std::result_of 可以用于推导函数返回值类型:
typedef double (*func)(); int main() { result_ of< func()>:: type f; // 由 func() 推导 其 结果 类型 } // 编译 选项: g++ -std= c++ 11 4- 3- 8. cpp
11、decltype 推导四规则
decltype 可以带双括号。如下:
int i; decltype( i) a; // a: int decltype(( i)) b; // b: int &, 无法 编译 通过
推导四规则如下:
1)最常见的情形,如前文所应用。如果 e 是一 个 没有带括号的标记符表达式( id- expression) 或者 类成员访问表达式, 那么 decltype( e) 就是 e 所 命名 的 实体 的 类型。 此外, 如果 e 是 一个 被重载的函数, 则 会 导致 编译 时 错误。
2)否则, 假设 e 的 类型 是 T, 如果 e 是 一个 将 亡 值( xvalue), 那么 decltype( e) 为 T&&。
3)否则, 假设 e 的 类型 是 T, 如果 e 是 一个 左 值, 则 decltype( e) 为 T&。
4)否则, 假设 e 的 类型 是 T, 则 decltype( e) 为 T。
int i = 4; int arr[ 5] = {0}; int *ptr = arr; struct S { double d; } s; void Overloaded( int); void Overloaded( char); // 重载 的 函数 int && RvalRef(); const bool Func( int); // 规则 1: 单个 标记 符 表达式 以及 访问 类 成员, 推导 为本 类型 decltype( arr) var1; // int[ 5], 标记 符 表达式 decltype( ptr) var2; // int*, 标记 符 表达式 decltype( s. d) var4; // double, 成员 访问 表达式 decltype( Overloaded) var5; // 无法 通过 编译, 是个 重载 的 函数 // 规则 2: 将 亡 值, 推导 为 类型 的 右 值 引用 decltype( RvalRef()) var6 = 1; // int&& // 规则 3: 左 值, 推导 为 类型 的 引用 decltype( true ? i : i) var7 = i; // int&, 三元 运算符, 这里 返回 一个 i 的 左 值 decltype(( i)) var8 = i; // int&, 带 圆括号 的 左 值 decltype(++ i) var9 = i; // int&, ++ i 返回 i 的 左 值 decltype( arr[ 3]) var10 = i; // int& []操作 返回 左 值 decltype(* ptr) var11 = i; // int& *操作 返回 左 值 decltype(" lval") var12 = "lval"; // const char(&)[ 9], 字符串 字面 常量 为 左 值 // 规则 4: 以上 都不 是, 推导 为本 类型 decltype( 1) var13; // int, 除 字符串 外 字面 常量 为 右 值 decltype( i++) var14; // int, i++ 返回 右 值 decltype(( Func( 1))) var15; // const bool, 圆括号 可以 忽略
可以使用 is_rvalue_reference、is_lvalue_reference 来判断是否是左右值引用。
#include < type_ traits> #include < iostream> using namespace std; int i = 4; int arr[ 5] = {0}; int *ptr = arr; int && RvalRef(); int main() { cout << is_ rvalue_ reference< decltype( RvalRef())>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype( true ? i : i)>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype(( i))>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype(++ i)>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype( arr[ 3])>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype(* ptr)>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype(" lval")>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype( i++)>:: value << endl; // 0 cout << is_ rvalue_ reference< decltype( i++)>:: value << endl; // 0 }
12、cv限制符的继承与冗余符号
与 auto 类型 推导 时不 能“ 带走” cv 限制 符 不同, decltype 是 能够“ 带走” 表达式 的 cv 限制 符 的。通过 is_const、is_volatile 可以判定 const、volatile 性。
using namespace std; const int ic = 0; volatile int iv; struct S { int i; }; const S a = {0}; volatile S b; volatile S* p = &b; int main() { cout << is_ const< decltype( ic)>:: value << endl; // 1 cout << is_ volatile< decltype( iv)>:: value << endl; // 1 cout << is_ const< decltype( a)>:: value << endl; // 1 cout << is_ volatile< decltype( b)>:: value << endl; // 1 cout << is_ const< decltype( a. i)>:: value << endl; // 0, 成员 不是 const cout << is_ volatile< decltype( p-> i)>:: value << endl; // 0, 成员 不是 volatile }
进行 类型 定义 时, 也会 允许 一些 冗余 的 符号。 比如 cv 限制 符 以及 引用,符号&, 通常 情况下, 如果 推 导出 的 类型 已经 有了 这些 属性, 冗余 的 符号 则 会被 忽略。
int i = 1; int & j = i; int * p = &i; const int k = 1; int main() { decltype( i) & var1 = i; decltype( j) & var2 = i; // 冗余 的&, 被 忽略 cout << is_ lvalue_ reference< decltype( var1)>:: value << endl;// 1, 是 左 值 引用 cout << is_ rvalue_ reference< decltype( var2)>:: value << endl;// 0, 不是 右 值 引用 cout << is_ lvalue_ reference< decltype( var2)>:: value << endl;// 1, 是 左 值 引用 decltype( p)* var3 = &i; // 无法 通过 编译 decltype( p)* var3 = &p; // var3 的 类型 是 int** auto* v3 = p; // v3 的 类型 是 int* v3 = &i; const decltype( k) var4 = 1; // 冗余 的 const, 被 忽略 }
这里 特别 要 注意 的 是 decltype( p)* 的 情况。 可以 看到, 在 定义 var3 变量 的 时候, 由于 p 的 类型 是 int*, 因此 var3 被 定义 为了 int** 类型。 这 跟 auto 声明 中,* 也可以 是 冗余 的 不同。 在 decltype 后的* 号, 并不 会被 编译器 忽略。
13、追踪返回类型的引入
C++ 11 引入 新语 法— 追踪 返回 类型。
template< typename T1, typename T2> auto Sum( T1 & t1, T2 & t2) -> decltype( t1 + t2) { return t1 + t2; }
我们 把 函数 的 返回 值 移至 参数 声明 之后, 复合 符号-> decltype( t1+ t2) 被称为 追踪 返回 类型。 而 原本 函数 返回 值 的 位置 由 auto 关键字 占据。 这样, 我们 就可以 让 编译器 来 推导 Sum 函数 模板 的 返回 类型 了。 而 auto 占位符 和-> return_ type 也就是 构成 追踪 返回 类型 函数 的 两个 基本 元素。
14、使用追踪返回类型的函数
使用追踪返回类型的时候,可以不必写明作用域。如下,InnerType不必写明其作用域。
class OuterType { struct InnerType { int i; }; InnerType GetInner(); InnerType it; }; // 可以 不 写 OuterType:: InnerType auto OuterType:: GetInner() -> InnerType { return it; }
返回 类型 后置, 使 模板 中的 一些 类型 推导 就成 为了 可能。如下:
template< typename T1, typename T2> auto Sum( const T1 & t1, const T2 & t2) -> decltype( t1 + t2) { return t1 + t2; } template < typename T1, typename T2> auto Mul( const T1 & t1, const T2 & t2) -> decltype( t1 * t2) { return t1 * t2; } int main() { auto a = 3; auto b = 4L; auto pi = 3. 14; auto c = Mul( Sum( a, b), pi); cout << c << endl; // 21. 98 }
追踪 返回 类型 的 另一个 优势 是 简化 函数 的 定义, 提高 代码 的 可读性。
#include < type_ traits> #include < iostream> using namespace std; // 有的 时候, 你会 发现 这是 面试 题 int (*(*pf())())() { return nullptr; } // auto (*)() -> int(*) () 一个 返回 函数 指针 的 函数( 假设 为 a 函数) // auto pf1() -> auto (*)() -> int (*)() 一个 返回 a 函数 的 指针 的 函数 auto pf1() -> auto (*)() -> int (*)() { return nullptr; } int main() { cout << is_ same< decltype( pf), decltype( pf1)>:: value << endl; // 1 }
使用 追踪 返回 类型, 可以 实现 参数 和 返回 类型 不 同时 的 转发。
double foo( int a) { return (double) a + 0. 1; } int foo( double b) { return (int) b; } template < class T> auto Forward( T t) -> decltype( foo( t)) { return foo( t); } int main() { cout << Forward( 2) << endl; // 2. 1 cout << Forward( 0. 5) << endl; // 0 }
以下内容是等价的。
auto (*fp)() -> int; int (*fp)(); auto (&fr)()->int; int (&fr)();
15、
16、
17、