【深刻理解C++11【4】】ios
一、基于范围的 for 循环程序员
C++98 中须要告诉编译器循环体界面范围。如for,或stl 中的for_each:算法
int main() { int arr[ 5] = { 1, 2, 3, 4, 5}; int * p; for (p = arr; p < arr + sizeof( arr)/ sizeof( arr[ 0]); ++ p){ *p *= 2; } for (p = arr; p < arr + sizeof( arr)/ sizeof( arr[ 0]); ++ p){ cout << *p << '\t'; } }
int action1( int & e){ e *= 2; } int action2( int & e){ cout << e << '\t'; } int main() { int arr[ 5] = { 1, 2, 3, 4, 5}; for_ each( arr, arr + sizeof( arr)/ sizeof( arr[ 0]), action1); for_ each( arr, arr + sizeof( arr)/ sizeof( arr[ 0]), action2); }
由 程序员 来讲 明 循环 的 范围是 多余 的, 也是 容易 犯错误 的。 而 C++ 11 也 引入 了 基于 范围 的 for 循环, 就能够 很好 地 解决 了 这个 问题。express
int main() { int arr[ 5] = { 1, 2, 3, 4, 5 }; for (int & e: arr) e *= 2; for (int & e: arr) cout << e << '\t'; }
上棕采用了引用的形式,也能够不采用引用的形式。编程
for (int e: arr) cout << e << '\t';
结合以前的 auto,会更简练。数组
for (auto e: arr) cout << e << '\t';
下述 代码 会 报错, 由于 做为参数传递而来的数组 a 的范围不能肯定, 所以 也就 不能 使用 基于 范围 循环 for 循环 对其 进行 迭代 的 操做。app
int func( int a[]) { for (auto e: a) cout << e; } int main() { int arr[] = {1, 2, 3, 4, 5}; func( arr); }
二、枚举:分门别类与数值的名字。less
觉的常量定义方式:ide
1)宏。宏 的 弱点 在于 其 定义 的 只是 预处理 阶段 的 名字, 若是 代码 中有 Male 或者 Female 的 字符串, 不管 在什么 位置 一概 将被 替换。 这 在 有的 时候 会 干扰 到 正常 的 代码,函数
#define Male 0 #define Female 1
2)枚举。相比于宏,会 获得 编译器 的 检查。且 不会有 干扰 正常 代码 的 尴尬。
enum { Male, Female };
3)静态常量。因为 是 静态 常量, 其 名字 做用域 也 被 很好 地 局限于 文件 内。
const static int Male = 0; const static int Female = 1;
三、有缺陷的枚举类型
C/ C++ 的 enum 有个 很“ 奇怪” 的 设定, 就是 具名( 有 名字) 的 enum 类型 的 名字, 以及 enum 的 成员 的 名字 都是 全局 可见 的。下面代码,Category 中的 General 和 Type 中的 General 都是 全局 的 名字, 所以 编译会报错。
enum Type { General, Light, Medium, Heavy }; enum Category { General, Pistol, MachineGun, Cannon };
匿名space的 enum仍是会污染全局空间。以下:
namespace T { enum Type { General, Light, Medium, Heavy }; } namespace { enum Category { General = 1, Pistol, MachineGun, Cannon }; } int main() { T:: Type t = T:: Light; if (t == General) // 忘记 使用 namespace cout << "General Weapon" << endl; return 0; }
C++98 中并不会阻止不一样enum 间的比较。
enum Type { General, Light, Medium, Heavy }; //enum Category { General, Pistol, MachineGun, Cannon }; // 没法 编译 经过, 重复 定义 了 General enum Category { Pistol, MachineGun, Cannon }; int main() { Type type1; if (type1 >= Pistol) cout << "It is not a pistol" << endl; }
C++98 中能够经过封闭来解决上述污染和类型比较的问题。
class Type { public: enum type { general, light, medium, heavy }; type val; public: Type( type t): val( t){} bool operator >= (const Type & t) { return val >= t. val; } static const Type General, Light, Medium, Heavy; }; const Type Type:: General( Type:: general); const Type Type:: Light( Type:: light); const Type Type:: Medium( Type:: medium); const Type Type:: Heavy( Type:: heavy); class Category { public: enum category { pistol, machineGun, cannon }; category val; public: Category( category c): val( c) {} bool operator >= (const Category & c) { return val >= c. val; } static const Category Pistol, MachineGun, Cannon; }; const Category Category:: Pistol( Category:: pistol); const Category Category:: MachineGun( Category:: machineGun); const Category Category:: Cannon( Category:: cannon); struct Killer { Killer( Type t, Category c) : type( t), category( c){} Type type; Category category; }; int main() { // 使用 类型 包装 后的 enum Killer notCool( Type:: General, Category:: MachineGun); // ... // ...其余 不少 代码... // ... if (notCool. type >= Type:: General) // 能够 经过 编译 cout << "It is not general" << endl; if (notCool. type >= Category:: Pistol) // 该 句 没法 编译 经过 cout << "It is not a pistol" << endl; // ... cout << is_ pod< Type>:: value << endl; // 0 cout << is_ pod< Category>:: value << endl; // 0 return 0; }
编译器 会 根据 数据类型 的 不一样 对 enum 应用 不一样 的 数据 长度。 在 咱们 对 g++ 的 测试 中, 普通 的 枚举 使用 了 4 字节 的 内存, 而 当 须要 的 时候, 会 拓展 为 8 字节。
enum C { C1 = 1, C2 = 2}; enum D { D1 = 1, D2 = 2, Dbig = 0xFFFFFFF0U }; enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFFFFLL}; int main() { cout << sizeof( C1) << endl; // 4 cout << Dbig << endl; // 编译器 输出 不一样, g++: 4294967280 cout << sizeof( D1) << endl; // 4 cout << sizeof( Dbig) << endl; // 4 cout << Ebig << endl; // 68719476735 cout << sizeof( E1) << endl; // 8 return 0; }
不一样的编译器,上例中 Dbig 的 输出 结果 将会 不一样: 使用 Visual C++ 编译程序 的 输出 结果 为– 16, 而使 用 g++ 来 编译 输出 为 4294967280。 这是 因为 Visual C++ 老是 使用 无符号 类型 做为 枚举 的 底层 实现, 而 g++ 会 根据 枚举 的 类型 进行 变更 形成 的。
四、强类型枚举(strong-typed enum) 以及 C++ 11 对 原有枚举类型的扩展
声明 强 类型 枚举 很是 简单, 只需 要在 enum 后加 上 关键字 class。
enum class Type { General, Light, Medium, Heavy };
强类型枚举有如下几个优点:
1)强做用域,强类型枚举成员的名称不会被输出到其你做用域空间。
2)转换限制,强类型枚举成员的值不能够与整形隐式地相互转换。
3)能够 指定 底层 类型。 强 类型 枚举 默认 的 底层 类型 为 int, 但也 能够 显 式 地 指定 底层 类型。
enum class Type: char { General, Light, Medium, Heavy };
下面是一个强类型枚举的例子:
enum class Type { General, Light, Medium, Heavy }; enum class Category { General = 1, Pistol, MachineGun, Cannon }; int main() { Type t = Type:: Light; t = General; // 编译 失败, 必须 使用 强 类型 名称 if (t == Category:: General) // 编译 失败, 必须 使用 Type 中的 General cout << "General Weapon" << endl; if (t > Type:: General) // 经过 编译 cout << "Not General Weapon" << endl; if (t > 0) // 编译 失败, 没法 转换 为 int 类型 cout << "Not General Weapon" << endl; if ((int) t > 0) // 经过 编译 cout << "Not General Weapon" << endl; cout << is_ pod< Type>:: value << endl; // 1 cout << is_ pod< Category>:: value << endl; // 1 return 0; }
设置较小的基本类型能够节省空间:
enum class C : char { C1 = 1, C2 = 2}; enum class D : unsigned int { D1 = 1, D2 = 2, Dbig = 0xFFFFFFF0U }; int main() { cout << sizeof( C:: C1) << endl; // 1 cout << (unsigned int) D:: Dbig << endl; // 编译器 输出 一致, 4294967280 cout << sizeof( D:: D1) << endl; // 4 cout << sizeof( D:: Dbig) << endl; // 4 return 0; }
C++11加强了原有枚举类型:
1)能够 跟 强 类型 枚举 类 同样, 显 式 地 由 程序员 来 指定。
enum Type: char { General, Light, Medium, Heavy };
2)扩展做用域。除了增长到父做用域外,还增长到自身做用域,二者等价。
enum Type { General, Light, Medium, Heavy }; Type t1 = General; Type t2 = Type:: General;
此外, 咱们 在 声明 强 类型 枚举 的 时候, 也能够 使用 关键字 enum struct。 事实上 enum struct 和 enum class 在 语法 上 没有 任何 区别( enum class 的 成员 没有 公有 私有 之分,
有 一点 比较 有趣 的 是 匿名 的 enum class。 因为 enum class 是 强 类型 做用域 的, 故匿名的 enum class 极可能什么都作不了。
enum class { General, Light, Medium, Heavy } weapon; int main() { weapon = General; // 没法 编译 经过 bool b = (weapon == weapon:: General); // 没法 编译 经过 return 0; }
五、显式内存管理。
常见内存管理问题:
1)野指针、重复释放。指向指向内存已被释放,但指针却还在被使用。
2)内存泄露。指针已经丢失,但其指向内存并未被释放。
C++98中的智能指针是 auto_ptr,不过 auto_ ptr 有 一些 缺点( 拷贝 时 返回 一个 左 值, 不能 调用 delete[] 等), 因此 在 C++ 11 标准中被废弃了。 C++ 11 标准 中 改用 unique_ ptr、 shared_ ptr 及 weak_ ptr 等 智能 指针 来自 动 回收 堆 分配 的 对象。
下面是 unique_ptr、shared_ptr 的例子:
#include < memory> #include < iostream> using namespace std; int main() { unique_ ptr< int> up1( new int( 11)); // 没法 复制 的 unique_ ptr unique_ ptr< int> up2 = up1; // 不能 经过 编译 cout << *up1 << endl; // 11 unique_ ptr< int> up3 = move( up1); // 如今 p3 是 数据 惟一 的 unique_ ptr 智能 指针 cout << *up3 << endl; // 11 cout << *up1 << endl; // 运行时 错误 up3. reset(); // 显 式 释放 内存 up1. reset(); // 不会 致使 运行时 错误 cout << *up3 << endl; // 运行时 错误 shared_ ptr< int> sp1( new int( 22)); shared_ ptr< int> sp2 = sp1; cout << *sp1 << endl; // 22 cout << *sp2 << endl; // 22 sp1. reset(); cout << *sp2 << endl; // 22 }
unique_ ptr 则是 一个 删除 了 拷贝 构造 函数、 保留 了 移动 构造 函数 的 指针 封装 类型。
shared_ptr 在实现 上 采用 了 引用 计数, 因此 一旦 一个 shared_ ptr 指针 放弃 了“ 全部权”( 失效), 其余 的 shared_ ptr 对对 象 内存 的 引用 并不 会 受到影响。
weak_ptr 可 以 指向 shared_ ptr 指针 指向 的 对象 内存, 却 并不 拥有 该 内存。 而使 用 weak_ ptr 成员 lock, 则 可 返回 其 指向 内存 的 一个 shared_ ptr 对象, 且 在 所指 对象 内存 已经 无效 时, 返回 指针 空 值( nullptr, 请 参见 7. 1 节)。 这 在 验证 share_ ptr 智能 指针 的 有效性 上 会很 有 做用。以下:
#include < memory> #include < iostream> using namespace std; void Check( weak_ ptr< int> & wp) { shared_ ptr< int> sp = wp. lock(); // 转换 为 shared_ ptr< int> if (sp != nullptr) cout << "still " << *sp << endl; else cout << "pointer is invalid." << endl; } int main() { shared_ ptr< int> sp1( new int( 22)); shared_ ptr< int> sp2 = sp1; weak_ ptr< int> wp = sp1; // 指向 shared_ ptr< int> 所指 对象 cout << *sp1 << endl; // 22 cout << *sp2 << endl; // 22 Check( wp); // still 22 sp1. reset(); cout << *sp2 << endl; // 22 Check( wp); // still 22 sp2. reset(); Check( wp); // pointer is invalid }
六、垃圾回收的分类。
垃圾回收的方式能够分为两大类:
1)基于引用计数。这种 方法 比较 难处理“ 环形 引用” 问题, 此外 因为 计数 带来 的 额外 开销 也 并不 小。
2)基于跟踪处理。跟踪 处理 的 垃圾 回收 机制 被 更为 普遍 地 应用。主要有如下几种方法:
a)标记 - 清除(Mark-Sweep)
这种 方法 的 特色 是 活的 对象 不会 被 移动, 可是 其 存在 会 出现 大量 的 内存 碎片 的 问题。
b)标记 - 整理(Mark-Compact)
这个 算法 标记 的 方法 和 标记- 清除 方法 同样, 可是 标记 完 以后, 再也不 遍历 全部 对象 清扫 垃圾 了, 而是 将 活的 对象 向“ 左” 靠 齐, 这就 解决 了 内存 碎片 的 问题。
c)标记 - 拷贝(Mark-Copy)
标记– 整理 算法 的 另外一种 实现。这种 算法 将 堆 空间 分为 两个 部分: From 和 To。
C++ 11 标准 也 开始 对 垃圾 回收 作了 必定 的 支持, 虽然 支持 的 程度 还 很是 有限。
七、C++ 与垃圾回收。
由于C++中能够自由移动指针,因此若是加入垃圾回收,有可能将有用的内存回收,从而致使重大内存问题。
int main() { int* p = new int; p += 10; // 移动 指针, 可能 致使 垃圾 回收 器 p -= 10; // 回收 原来 指向 的 内存 *p = 10; // 再次 使用 本来 相同 的 指针 则 可能 无效 }
下例也是同样,隐藏原始指针,可能致使内存被回收问题。
int main() { int *p = new int; int *q = (int*)( reinterpret_ cast< long long>( p) ^ 2012); // q 隐藏 了 p // 作 一些 其余 工做, 垃圾 回收 器 可能 已经 回收 了 p 指向 对象 q = (int*)( reinterpret_ cast< long long>( q) ^ 2012); // 这里 的 q == p *q = 10; }
八、C++11 与最小垃圾回收支持。
截至2013年, 几乎没有 编译器 实现 了 最小 垃圾 回收 支持, 甚至 连 get_ pointer_ safety 这个 函数 接口 都 还没 实现。
declare_ reachable() 显 式 地 通知 垃圾 回收 器 某一个 对象 应被 认为 可达 的, 即便 它的 全部 指针 都对 回收 器 不 可见。 undeclare_ reachable() 则 能够 取消 这种 可达 声明。
#include < memory> using namespace std; int main() { int *p = new int; declare_ reachable( p); // 在 p 被 隐藏 以前 声明 为 可达 的 int *q = (int*)(( long long) p ^ 2012); // 解除 可达 声明 q = undeclare_ reachable< int>(( int*)(( long long) q ^ 2012)); *q = 10; }
有的 时候 程序员 会 选择 在 一大 片 连续 的 堆 内存 上 进行 指针式 操做, 为了 让 垃圾 回收 器 不关心 该 区域, 也能够 使用 declare_ no_ pointers 及 undeclare_ no_ pointers 函数 来 告诉 垃圾 回收 器 该 内存 区域 不存在 有效 的 指针。
void declare_ no_ pointers( char *p, size_ t n) noexcept;
void undeclare_ no_ pointers( char *p, size_ t n) noexcept;
九、垃圾回收的兼容性。
C++ 11 标准 中 对 指针 的 垃圾 回收 支持 仅限 于 系统 提供 的 new 操做 符 分配 的 内存, 而 malloc 分配 的 内存 则 会被 认为 老是 可达 的, 即 不管 什么时候 垃圾 回收 器 都不 予 回收。 所以 使用 malloc 等 的 较老 代码 的 堆 内存 仍是 必须 由 程序员 本身 控制。
十、运行时常量性与编译时常量性。
const用于保证运行期常量性,但有时候,咱们须要的倒是编译时的常量性,这是const关键字没法保证的。
const int GetConst() { return 1; } void Constless( int cond) { int arr[ GetConst()] = {0}; // 没法 经过 编译 enum { e1 = GetConst(), e2 }; // 没法 经过 编译 switch (cond) { case GetConst(): // 没法 经过 编译 break; default: break; } }
上述代码,咱们发现, 不管 将 GetConst 的 结果 用于 须要 初始化 数组 Arr 的 声明 中, 仍是 用于 匿名 枚举 中, 或 用于 switch- case 的 case 表达式 中, 编译器 都会 报告 错误。
发生 这样 错误 的 缘由 如 咱们 上面 提到 的 同样, 这些 语句 都 需 要的 是 编译 时期 的 常 量值。 而 const 修饰 的函数 返回 值, 只 保证 了 在 运行时 期内 其 值 是 不能够 被 更改 的。 这是 两个 彻底 不一样 的 概念。
enum BitSet { V0 = 1 << 0, V1 = 1 << 1, V2 = 1 << 2, VMAX = 1 << 3 }; // 重定 义 操做 符"|", 以 保证 返回 的 BitSet 值 不超过 枚举 的 最大值 const BitSet operator|( BitSet x, BitSet y) { return static_ cast< BitSet>((( int) x | y) & (VMAX - 1)); } template < int i = V0 | V1> // 没法 经过 编译 void LikeConst(){}
上述代码,咱们将 V0| V1 做为 非 类型 模板 函数 的 默认 模板 参数, 则 会 致使 编译 错误。 这 一样 是由 需 要的 是 编译 时 常量 所 致使 的。
宏能够解决上述问题,但这种 简单 粗暴 的 作法 即便 有效, 也 会把 C++ 拉回“ 石器 时代”。 C++ 11 中 对 编译 时期 常量 的 回答 是 constexpr, 即 常量表达式( constant expression)。
constexpr int GetConst() { return 1; }
在 函数 表达式 前 加上 constexpr 关键字 便可。 有了 常量 表达式 这样 的 声明, 编译器 就可 以在 编译 时期 对 GetConst 表达式 进行 值 计算( evaluation), 从而 将其 视为 一个 编译 时期 的 常量。
这样一来 上述代码 数组 Arr、 匿名 枚举 的 初始化 以及 switch- case 的 case 表达式 经过 编译 都 再也不 是 问题。
十一、常量表达式函数。
并不是 全部 的 函数 都有 资格 成为 常量 表达式 函数。 事实上, 常量 表达式 函数 的 要求 很是 严格, 总结 起来, 大概 有 如下 几点:
1)函数体只有单一的return返回语句。例如,下面的的写会编译错误:
constexpr int data() { const int i = 1; return i; }
不过 一些 不会 产生 实际 代码 的 语句 在 常量 表达式 函数 中 使用 下, 倒 不会 致使 编译器 的“ 抱怨”。
constexpr int f( int x) { static_ assert( 0 == 0, "assert fail."); return x; }
上面例子 可以 经过 编译。 而 其余 的, 好比 using 指令、 typedef 等 也 一般 不会 形成 问题。
2)函数必须返回值(不能是void函数)。例如,下面的写法,就不能经过:
constexpr void f(){}
3)在使用前必须已有定义。
constexpr int f(); int a = f(); const int b = f(); constexpr int c = f(); // 没法 经过 编译 constexpr int f() { return 1; } constexpr int d = f();
constexpr 不算重载,会致使编译错误。
constexpr int f(); int f();
4)return 返回 语句 表达式 中 不能 使用 非 常量 表达式 的 函数、 全局 数据, 且 必须 是一 个 常量 表达式。例如如下constexpr不能经过编译。
const int e(){ return 1;} constexpr int g(){ return e(); } // 编译错误 int g = 3; constexpr int h() { return g; } // 编译错误
另外,constexpr中赋值语句也是不容许的。如下会编译错误。
constexpr int k( int x) { return x = 1; }
十二、常量表达式的值。
C++ 11 标准 中, constexpr 关键字 是 不能 用于 修饰 自定义 类型 的 定义 的。如下代码,没法经过编译。
constexpr struct MyType {int i; } constexpr MyType mt = {0};
正确地 作法 是, 定义 自定义 常量构造函数( constent- expression constructor)。
struct MyType { constexpr MyType( int x): i( x){} int i; }; constexpr MyType mt = {0};
常量 表达式 的 构造 函数 也有 使 用上 的 约束, 主 要的 有 如下 两点:
1)函数体必须为空。
2)初始化列表只能由常量表达式来赋值。例如,如下常量构造函数是错误的。
int f(); struct MyType {int i; constexpr MyType():i(f()){}};
常量构造函数、成员函数的区别:
struct Date { constexpr Date(int y, int m, int d): year(y), month(m),day(d){} constexpr int GetYear() { return year; } constexpr int GetMonth() { return month; } constexpr int GetDay() { return day; } private: int year; int month; int day; } constexpr Date PRCfound {1949,10,1}; constexpr int foundmont = PRCfound.GetMonth(); int main() { count << foundmonth << endl; // 10 }
C++11中,不容许常量表达式做用于 virtual 成员函数。
1三、常量表达式的其余应用
常量表达式能够用于模板函数。不过因为模板中类型的不肯定性,因此模板函数是否会被实例化为一个可以知足编译时常量性的版本一般也是未知的。
C++11规定,当声明为常量表达式的模板函数后,而某个该模板函数的实例化结果不知足常量表达式的需求的话,constexpr会被自动忽略。该实例化后的函数将成为一个普通函数。
struct NotLiteral{ NotLiteral(){i=5;} int i; }; NotLiteral nl; template<typename T> constexpr T ConstExp(T t){ return t; } void g(){ NotLiteral nl; NotLiteral nl1 = ConstExp(nl); constexpr NotLiteral nl2 = ConstExp(nl); // 没法经过编译 constexpr int a = ConstExp(1); }
上述代码,NotLiteral不是一个定义了常量表达式构造函数的类型,ConstExp一旦以 NotLiteral为参数的话,那么其 constexpr关键字将被忽略,因此 ConstExp<NotLiteral>函数不是一个常量表达式函数。
C++11 标准对常量表达式支持至少 512层递归。
constexpr int Fibonacci(int n){ return (n==1)?1:((n--2)?1:Fibonacci(n-1)+Fibonacci(n-2)); } int main(){ int fib[] = { Fibonacci(11), Fibonacci(12), Fibonacci(13), Fibonacci(14), Fibonacci(15), Fibonacci(16) }; for (int i : fib) cout<<i<<endl; }
在C++98中,上述常量表达式函数递归能够经过模板元编程来完成。
template<long num> struct Fibonacci{ static const long val = Fibonacci<num-1>::val + Fibonacci<num-2>::val; } template<> struct Fibonacci<2> { static const long val =1; } template<> struct Fibonacci<1> { static const long val =1; } template<> struct Fibonacci<0> { static const long val =0; }
1四、变长函数和变长的模板参数
C++98 中使用 va_start、va_arg、 va_end 来实现变长函数参数功能。
double SumOfFloat(int count, ...){ va_list ap; ouble sum = 0; va_start(ap, count); for(int i = 0; i < count; i++) sum++ va_arg(ap, double); va_end(ap); return sum(); } int main(){ return printf("%f\n", SumOfFloat(3,1.2f,3.4,5.6)); }
C++98要求函数模板始终具备数目肯定的模板参数。
1五、 变长模板:模板参数包和函数参数包
标识符前加三个点,表示该参数是变长的。Elements被称为模板参数包(template parameter pack)。
template <typename... Elements>
class tuple;
模板参数包也能够是非类型的,例如:
template<int... A> class NonTypeVariadicTemplate{}; NonTypeVariadicTemplate<1,0,2> ntvt;
解包(unpack)在尾部添加三个点。下面这个叫包扩展(pack expansion)表达式。
template<typename... A> class Template : private B<A...>{};
上面只是展开了 A...,但没有使用展开后的内容。Tunple 的实现中,展现了一种使用包括展的方法,数学概括法(递归)。这与模板元编程,constexpr很是类似。
// 变长模板的声明 template <typename... Elements> class tuple; // 递归的偏特化的定义 template <typename Head, typename... Tail> class tuple<Head, Tail...>:private tuple<Tail...>{ Head head; }; // 边界条件 template<> class tuple<>{};
下面是一个使用非类型模板的例子。
// 声明 template <long... nums> struct Multiply; // 偏特化 template <long first, long... last> struct Multiply<first, last...>{ static const long val = first*Multiply<last...>::val; }; // 边界条件 template<> struct Multiply<>{ static const long val = 1; };
C++11中,还能够声明变长模板函数。变长模板函数的参数能够声明成为函数参数包(function parameter pack)。
template<typename... T> void f(T... args);
C++11中,标准要求函数参数包必须惟一,且是函数最后一个参数(模板参数包没有这样的要求)。
有了模板参数包、函数参数包,就能够实现C中变长函数的功能了。
void Printf(const char* s) { while(*s){ if(*s=='%'&&*++s!='%') throw runtime_error("invalid format string"); cout<<*s++; } } template<typename T, typename... Args> void Printf(const char*s, T value, Args... args) { while(*s){ if(*s=='%'&&*++s!='%'){ cout<<value; return Printf(++s, args...); } cout<<*s++; } throw runtime_error("extra arguments provided to Printf"); } int main(){ Printf("hello %s\n", string("world")); // hello world }
1六、变长模板:进阶
注意到前面的程序,包扩展形式以下:
template<typename... A> class T: private B<A...> {}; // 上述模板 T<X,Y> 将会解包为: class T<X,Y>:private B<X,Y> {}; template<typename... A> class T : private B<A>...{}; // 上述模板 T<X,Y> 将会解包为: class T<X,Y>:private B<X>, private B<Y>{};
也即,解包发生成继承基类模板时。B<A...> pack expasion 发生在<>内。而 B<A>... pack expasion 发生在 <>外。
类型的解包也能够发生在使用模板函数时。以下:
template<truename... T> void DummyWrapper(T... t){} template<typename T> T pr(T t){ cout << t; return t; } template<typename... A> void VTPrint(A... a){ DummyWrapper(pr(a)...); // 包扩展为 pr(1), pr(", "), ..., pr(", abc\n") } int main() { VTPrint(1,", ", 1.2, ", abc\n"); }
C++11中引入 了新操做符“sizeof...”(sizeof后面加上了3个小点),其做用是计算参数包中的参数个数。下面是一个示例:
template<class...A> void Print(A... arg){ assert(false); // 非6参数偏特化都会默认assert(false) } // 特化6参数的版本 void Print(int a1, int a2, int a3, int a4, int a5, int a6){ cout<<a1<<", "<<a2<<", "<<a3<<", " <<a4<<", "<<a5<<", "<<a6<<endl; } template<class...A> int Vaargs(A... args){ int size = sizeof...(A); // 计算变长包的长度 switch(size){ case 0: Print(99, 99, 99, 99, 99, 99); break; case 1: Print(99,99,args...,99,99,99); break; case 2: Print(99,99,args...,99,99); break; case 3: Print(args...,99,99,99); break; case 4: Print(99,args...,99); break; case 5: Print(99,args...); break; case 6: Print(args...); break; case default: Print(0,0,0,0,0,0); } } int main(void) { Vaargs(); // 99,99,99,99,99,99 Vaargs(1); // 99,99,1,99,99,99 Vaargs(1,2); // 99,99,1,2,99,99 Vaargs(1,2,3); // 1,2,3,99,99,99 Vaargs(1,2,3,4); // 99,1,2,3,4,99 Vaargs(1,2,3,4,5); // 99,1,2,3,4,5 Vaargs(1,2,3,4,5,6); // 99,1,2,3,4,5,6 Vaargs(1,2,3,4,5,6,7); // 0,0,0,0,0,0 return 0; }
使用模板作变长模板参数包:
template<typename I, template<typename> class... B> struct Container{}; template<typename I, template<typename> class A, template<typename> class... B> struct Container<I, A,B...>{ A<I> a; Container<I,B...> b; } template<truename I> struct Container<I>{};
下述代码会编译错误,模板参数包不是变长模板类的最后一个参数。
template<class...A, class...B> struct Container{}; template<class...A, class B> struct Container{};