深入理解C++11【4】

时间:2022-08-31 14:36:56

深入理解C++11【4】

1、基于范围的 for 循环

  C++98 中需要告诉编译器循环体界面范围。如for,或stl 中的for_each:

int main()
{
int arr[ ] = { , , , , };
int * p;
for (p = arr; p < arr + sizeof( arr)/ sizeof( arr[ ]); ++ p){
*p *= ;
} for (p = arr; p < arr + sizeof( arr)/ sizeof( arr[ ]); ++ p){
cout << *p << '\t';
}
}
int action1( int & e){ e *= ; }
int action2( int & e){ cout << e << '\t'; } int main() {
int arr[ ] = { , , , , };
for_ each( arr, arr + sizeof( arr)/ sizeof( arr[ ]), action1);
for_ each( arr, arr + sizeof( arr)/ sizeof( arr[ ]), action2);
}

  由 程序员 来说 明 循环 的 范围是 多余 的, 也是 容易 犯错误 的。 而 C++ 11 也 引入 了 基于 范围 的 for 循环, 就可以 很好 地 解决 了 这个 问题。

int main()
{
int arr[ ] = { , , , , };
for (int & e: arr) e *= ;
for (int & e: arr) cout << e << '\t';
}

  上棕采用了引用的形式,也可以不采用引用的形式。

for (int e: arr) cout << e << '\t';

  结合之前的 auto,会更简练。

for (auto e: arr) cout << e << '\t';

  下述 代码 会 报错, 因为 作为参数传递而来的数组 a 的范围不能确定, 因此 也就 不能 使用 基于 范围 循环 for 循环 对其 进行 迭代 的 操作。

int func( int a[])
{
for (auto e: a) cout << e;
} int main()
{
int arr[] = {, , , , };
func( arr);
}

2、枚举:分门别类与数值的名字。

  觉的常量定义方式:

  1)宏。宏 的 弱点 在于 其 定义 的 只是 预处理 阶段 的 名字, 如果 代码 中有 Male 或者 Female 的 字符串, 无论 在什么 位置 一律 将被 替换。 这 在 有的 时候 会 干扰 到 正常 的 代码,

#define Male 0
#define Female 1

  2)枚举。相比于宏,会 得到 编译器 的 检查。且 不会有 干扰 正常 代码 的 尴尬。

enum { Male, Female };

  3)静态常量。由于 是 静态 常量, 其 名字 作用域 也 被 很好 地 局限于 文件 内。

const static int Male = ;
const static int Female = ;

3、有缺陷的枚举类型

  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 = , Pistol, MachineGun, Cannon };
} int main()
{
T:: Type t = T:: Light;
if (t == General) // 忘记 使用 namespace
cout << "General Weapon" << endl;
return ;
}

  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 ;
}

  编译器 会 根据 数据类型 的 不同 对 enum 应用 不同 的 数据 长度。 在 我们 对 g++ 的 测试 中, 普通 的 枚举 使用 了 4 字节 的 内存, 而 当 需要 的 时候, 会 拓展 为 8 字节。

enum C { C1 = , C2 = };
enum D { D1 = , D2 = , Dbig = 0xFFFFFFF0U };
enum E { E1 = , E2 = , 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 ;
}

  不同的编译器,上例中 Dbig 的 输出 结果 将会 不同: 使用 Visual C++ 编译程序 的 输出 结果 为– 16, 而使 用 g++ 来 编译 输出 为 4294967280。 这是 由于 Visual C++ 总是 使用 无符号 类型 作为 枚举 的 底层 实现, 而 g++ 会 根据 枚举 的 类型 进行 变动 造成 的。

4、强类型枚举(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 = , 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 > ) // 编译 失败, 无法 转换 为 int 类型
cout << "Not General Weapon" << endl; if ((int) t > ) // 通过 编译
cout << "Not General Weapon" << endl; cout << is_ pod< Type>:: value << endl; // 1
cout << is_ pod< Category>:: value << endl; // 1 return ;
}

  设置较小的基本类型可以节省空间:

enum class C : char { C1 = , C2 = };
enum class D : unsigned int { D1 = , D2 = , 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 ;
}

  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 ;
}

5、显式内存管理。

  常见内存管理问题:

  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( )); // 无法 复制 的 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( ));
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( ));
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
}

6、垃圾回收的分类。

  垃圾回收的方式可以分为两大类:

  1)基于引用计数。这种 方法 比较 难处理“ 环形 引用” 问题, 此外 由于 计数 带来 的 额外 开销 也 并不 小。

  2)基于跟踪处理。跟踪 处理 的 垃圾 回收 机制 被 更为 广泛 地 应用。主要有以下几种方法:

    a)标记 - 清除(Mark-Sweep)

      这种 方法 的 特点 是 活的 对象 不会 被 移动, 但是 其 存在 会 出现 大量 的 内存 碎片 的 问题。

    b)标记 - 整理(Mark-Compact)

      这个 算法 标记 的 方法 和 标记- 清除 方法 一样, 但是 标记 完 之后, 不再 遍历 所有 对象 清扫 垃圾 了, 而是 将 活的 对象 向“ 左” 靠 齐, 这就 解决 了 内存 碎片 的 问题。

    c)标记 - 拷贝(Mark-Copy)

      标记– 整理 算法 的 另一种 实现。这种 算法 将 堆 空间 分为 两个 部分: From 和 To。

  C++ 11 标准 也 开始 对 垃圾 回收 做了 一定 的 支持, 虽然 支持 的 程度 还 非常 有限。

7、C++ 与垃圾回收。

  因为C++中可以*移动指针,所以如果加入垃圾回收,有可能将有用的内存回收,从而导致重大内存问题。

int main()
{
int* p = new int;
p += ; // 移动 指针, 可能 导致 垃圾 回收 器
p -= ; // 回收 原来 指向 的 内存
*p = ; // 再次 使用 原本 相同 的 指针 则 可能 无效
}

  下例也是一样,隐藏原始指针,可能导致内存被回收问题。

int main()
{
int *p = new int;
int *q = (int*)( reinterpret_ cast< long long>( p) ^ ); // q 隐藏 了 p
// 做 一些 其他 工作, 垃圾 回收 器 可能 已经 回收 了 p 指向 对象
q = (int*)( reinterpret_ cast< long long>( q) ^ ); // 这里 的 q == p
*q = ;
}

8、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 ^ ); // 解除 可达 声明
q = undeclare_ reachable< int>(( int*)(( long long) q ^ ));
*q = ;
}

  有的 时候 程序员 会 选择 在 一大 片 连续 的 堆 内存 上 进行 指针式 操作, 为了 让 垃圾 回收 器 不关心 该 区域, 也可以 使用 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;

9、垃圾回收的兼容性。

  C++ 11 标准 中 对 指针 的 垃圾 回收 支持 仅限 于 系统 提供 的 new 操作 符 分配 的 内存, 而 malloc 分配 的 内存 则 会被 认为 总是 可达 的, 即 无论 何时 垃圾 回收 器 都不 予 回收。 因此 使用 malloc 等 的 较老 代码 的 堆 内存 还是 必须 由 程序员 自己 控制。

10、运行时常量性与编译时常量性。

  const用于保证运行期常量性,但有时候,我们需要的却是编译时的常量性,这是const关键字无法保证的。

const int GetConst() { return ; } 

void Constless( int cond)
{
int arr[ GetConst()] = {}; // 无法 通过 编译
enum { e1 = GetConst(), e2 }; // 无法 通过 编译
switch (cond) {
case GetConst(): // 无法 通过 编译
break;
default:
break;
}
}

  上述代码,我们发现, 无论 将 GetConst 的 结果 用于 需要 初始化 数组 Arr 的 声明 中, 还是 用于 匿名 枚举 中, 或 用于 switch- case 的 case 表达式 中, 编译器 都会 报告 错误。

  发生 这样 错误 的 原因 如 我们 上面 提到 的 一样, 这些 语句 都 需 要的 是 编译 时期 的 常 量值。 而 const 修饰 的函数 返回 值, 只 保证 了 在 运行时 期内 其 值 是 不可以 被 更改 的。 这是 两个 完全 不同 的 概念。

enum BitSet {
V0 = << ,
V1 = << ,
V2 = << ,
VMAX = <<
}; // 重定 义 操作 符"|", 以 保证 返回 的 BitSet 值 不超过 枚举 的 最大值
const BitSet operator|( BitSet x, BitSet y) {
return static_ cast< BitSet>((( int) x | y) & (VMAX - ));
} template < int i = V0 | V1> // 无法 通过 编译
void LikeConst(){}

  上述代码,我们将 V0| V1 作为 非 类型 模板 函数 的 默认 模板 参数, 则 会 导致 编译 错误。 这 同样 是由 需 要的 是 编译 时 常量 所 导致 的。

  宏可以解决上述问题,但这种 简单 粗暴 的 做法 即使 有效, 也 会把 C++ 拉回“ 石器 时代”。 C++ 11 中 对 编译 时期 常量 的 回答 是 constexpr, 即 常量表达式( constant expression)

constexpr int GetConst() { return ; }

  在 函数 表达式 前 加上 constexpr 关键字 即可。 有了 常量 表达式 这样 的 声明, 编译器 就可 以在 编译 时期 对 GetConst 表达式 进行 值 计算( evaluation), 从而 将其 视为 一个 编译 时期 的 常量。

  这样一来 上述代码 数组 Arr、 匿名 枚举 的 初始化 以及 switch- case 的 case 表达式 通过 编译 都 不再 是 问题。

11、常量表达式函数。

  并非 所有 的 函数 都有 资格 成为 常量 表达式 函数。 事实上, 常量 表达式 函数 的 要求 非常 严格, 总结 起来, 大概 有 以下 几点:

  1)函数体只有单一的return返回语句。例如,下面的的写会编译错误:

constexpr int data() { const int i = ; return i; }

  不过 一些 不会 产生 实际 代码 的 语句 在 常量 表达式 函数 中 使用 下, 倒 不会 导致 编译器 的“ 抱怨”。

constexpr int f( int x)
{
static_ assert( == , "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 ; }
constexpr int d = f();

  constexpr 不算重载,会导致编译错误。

constexpr int f();
int f();

  4)return 返回 语句 表达式 中 不能 使用 非 常量 表达式 的 函数、 全局 数据, 且 必须 是一 个 常量 表达式。例如以下constexpr不能通过编译。

const int e(){ return ;}
constexpr int g(){ return e(); } // 编译错误 int g = ;
constexpr int h() { return g; } // 编译错误

  另外,constexpr中赋值语句也是不允许的。以下会编译错误。

constexpr int k( int x) { return x = ; }

12、常量表达式的值。

  C++ 11 标准 中, constexpr 关键字 是 不能 用于 修饰 自定义 类型 的 定义 的。以下代码,无法通过编译。

constexpr struct MyType {int i; }
constexpr MyType mt = {};

  正确地 做法 是, 定义 自定义 常量构造函数( constent- expression constructor)。

struct MyType
{
constexpr MyType( int x): i( x){}
int i;
}; constexpr MyType mt = {};

  常量 表达式 的 构造 函数 也有 使 用上 的 约束, 主 要的 有 以下 两点:

  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 {,,};
constexpr int foundmont = PRCfound.GetMonth();
int main()
{
count << foundmonth << endl; //
}

  C++11中,不允许常量表达式作用于 virtual 成员函数。

13、常量表达式的其他应用

  常量表达式可以用于模板函数。不过由于模板中类型的不确定性,所以模板函数是否会被实例化为一个能够满足编译时常量性的版本通常也是未知的。  

  C++11规定,当声明为常量表达式的模板函数后,而某个该模板函数的实例化结果不满足常量表达式的需求的话,constexpr会被自动忽略。该实例化后的函数将成为一个普通函数。

struct NotLiteral{
NotLiteral(){i=;}
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();
}

   上述代码,NotLiteral不是一个定义了常量表达式构造函数的类型,ConstExp一旦以 NotLiteral为参数的话,那么其 constexpr关键字将被忽略,所以 ConstExp<NotLiteral>函数不是一个常量表达式函数。

  C++11 标准对常量表达式支持至少 512层递归。

constexpr int Fibonacci(int n){
return (n==)?:((n--)?:Fibonacci(n-)+Fibonacci(n-));
} int main(){
int fib[] = {
Fibonacci(), Fibonacci(),
Fibonacci(), Fibonacci(),
Fibonacci(), Fibonacci()
}; for (int i : fib)
cout<<i<<endl;
}

  在C++98中,上述常量表达式函数递归可以通过模板元编程来完成。

template<long num>
struct Fibonacci{
static const long val = Fibonacci<num->::val + Fibonacci<num->::val;
}
template<> struct Fibonacci<> { static const long val =; }
template<> struct Fibonacci<> { static const long val =; }
template<> struct Fibonacci<> { static const long val =; }

14、变长函数和变长的模板参数

  C++98 中使用 va_start、va_arg、 va_end 来实现变长函数参数功能。

double SumOfFloat(int count, ...){
va_list ap;
ouble sum = ;
va_start(ap, count);
for(int i = ; i < count; i++)
sum++ va_arg(ap, double);
va_end(ap); return sum();
} int main(){
return printf("%f\n", SumOfFloat(,1.2f,3.4,5.6));
}

  C++98要求函数模板始终具有数目确定的模板参数。

15、 变长模板:模板参数包和函数参数包

  标识符前加三个点,表示该参数是变长的。Elements被称为模板参数包(template parameter pack)

template <typename... Elements>
class tuple;

  模板参数包也可以是非类型的,例如:

template<int... A> class NonTypeVariadicTemplate{};
NonTypeVariadicTemplate<,,> 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 = ;
};

  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
}

16、变长模板:进阶

  注意到前面的程序,包扩展形式如下:

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.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 : Print(, , , , , );
break;
case : Print(,,args...,,,);
break;
case : Print(,,args...,,);
break;
case : Print(args...,,,);
break;
case : Print(,args...,);
break;
case : Print(,args...);
break;
case : Print(args...);
break;
case default:
Print(,,,,,);
}
} int main(void)
{
Vaargs(); // 99,99,99,99,99,99
Vaargs(); // 99,99,1,99,99,99
Vaargs(,); // 99,99,1,2,99,99
Vaargs(,,); // 1,2,3,99,99,99
Vaargs(,,,); // 99,1,2,3,4,99
Vaargs(,,,,); // 99,1,2,3,4,5
Vaargs(,,,,,); // 99,1,2,3,4,5,6
Vaargs(,,,,,,); // 0,0,0,0,0,0
return ;
}

  使用模板做变长模板参数包:

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{};

深入理解C++11【4】的更多相关文章

  1. C&plus;&plus; 11学习和掌握 ——《深入理解C&plus;&plus; 11:C&plus;&plus;11新特性解析和应用》读书笔记&lpar;一&rpar;

    因为偶然的机会,在图书馆看到<深入理解C++ 11:C++11新特性解析和应用>这本书,大致扫下,受益匪浅,就果断借出来,对于其中的部分内容进行详读并亲自编程测试相关代码,也就有了整理写出 ...

  2. 深入理解C&plus;&plus;11【5】

    [深入理解C++11[5]] 1.原子操作与C++11原子类型 C++98 中的原子操作.mutex.pthread: #include<pthread.h> #include <i ...

  3. 深入理解C&plus;&plus;11【3】

    [深入理解C++11[3]] 1.POD类型 Plain Old Data. Plain 表示 了POD是个普通的类型.C++11将POD划分为两个基本概念的合集: 1)平凡的(trivial) 2) ...

  4. 深入理解C&plus;&plus;11【2】

    [深入理解C++11[2]] 1.继承构造函数. 当基类拥有多个构造函数的时候,子类不得不一一实现. C++98 可以使用 using 来使用基类的成员函数. #include < iostre ...

  5. 深入理解C&plus;&plus;11

    [深入理解C++11] 1.很多 现实 的 编译器 都 支持 C99 标准 中的__ func__ 预定 义 标识符 功能, 其 基本 功能 就是 返回 所在 函数 的 名字. 编译器 会 隐式 地 ...

  6. 发现《深入理解C&plus;&plus;11》中扩展的friend代码的错误

    目前在总结现代C++的新特性,看了<深入理解C++11>这本书. 今天看到扩展的friend语法这一节,遇到了问题.本节电子版内容参见:https://book.2cto.com/2013 ...

  7. c&plus;&plus;学习书籍推荐《深入理解C&plus;&plus;11 C&plus;&plus;11新特性解析与应用》下载

    百度云及其他网盘下载地址:点我 编辑推荐 <深入理解C++11:C++11新特性解析与应用>编辑推荐:C++标准委员会成员和IBM XL编译器中国开发团队共同撰写,权威性毋庸置疑.系统.深 ...

  8. 理解C&plus;&plus;11正则表达式(2)

    今天有幸(2016/3/19)在上海参加了C++交流会,见到了梦寐已久想见的*C++大神老师侯捷,心情十分的激动.侯老师对C++理解的深刻,让人叹为观止.以为他教学的严谨,说话方式娓娓道来,听着非常 ...

  9. 理解c&plus;&plus;11正则表达式 (1)

    概要 C++11提出了正则表达式这个概念,只需在头文件中包含#include<regex>即可.我们可以完成: Match 将整个输入拿来比对匹配某个正则表达式 Search 查找与正则表 ...

随机推荐

  1. uva 11401 Triangle Counting

    // uva 11401 Triangle Counting // // 题目大意: // // 求n范围内,任意选三个不同的数,能组成三角形的个数 // // 解题方法: // // 我们设三角巷的 ...

  2. VHD更新命令(打补丁)

    DISM 查看vhd文件信息:dism /get-imageinfo /imagefile:e:\vhd\win2008r2.vhdxdism /get-imageinfo /imagefile:e: ...

  3. Python读写文件需要注意的地方 2015-03-31 23&colon;19 69人阅读 评论&lpar;0&rpar; 收藏

    <span style="font-family: 'Microsoft YaHei'; background-color: rgb(255, 255, 255);"> ...

  4. 协议系列之HTTP协议

    什么是HTTP\HTTPS HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写.HTTP协议用于从WWWserver传输超文本到本地浏览器的传输协议,它能使浏览 ...

  5. IOS — 关于Socket传输文件需要自定义延时或者包大小的情况

    1. 首先导入头文件 #include <stdio.h> #include <errno.h> #include <string.h> #include < ...

  6. Python并发编程之多线程使用

    目录 一 开启线程的两种方式 二 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别 三 练习 四 线程相关的其他方法 五 守护线程 六 Python GIL(Global Interpret ...

  7. Linux 技巧:让进程在后台可靠运行的几种方法【转】

    我们经常会碰到这样的问题,用 telnet/ssh 登录了远程的 Linux 服务器,运行了一些耗时较长的任务, 结果却由于网络的不稳定导致任务中途失败.如何让命令提交后不受本地关闭终端窗口/网络断开 ...

  8. 用Canvas实现一些简单的图片滤镜

    1.灰度滤镜 对于灰度滤镜的实现一般有三种算法 1) 最大值法:即新的颜色值R=G=B=Max(R,G,B),通过这种方法处理后的图片看起来亮度值偏高. 2) 平均值法:即新的颜色值R=G=B=(R+ ...

  9. 怎样远程访问 MySQL

    比如我在PC上安装有 phpmyadmin, 图形界面很友好,我的MySQL 在一台Centos 7.5服务器上,很自然的想到用phpmyadmin 去登录linux上的MySQL. 但是折腾了很久也 ...

  10. 在线检测域名或者ip的端口是否开放&lpar;http&colon;&sol;&sol;coolaf&period;com&sol;tool&sol;port&rpar;

    http://coolaf.com/tool/port