C++templates第六章移动语义及enable_if<>
C++templates第六章移动语义及enable_if<>
Hoshea Zhang移动语义(movesemantics)是 C++11 引入的一个重要特性。在 copy 或者赋值的时候,可以 通过它将源对象中的内部资源 move(“steal”)到目标对象,而不是 copy 这些内容。当然 这样做的前提是源对象不在需要这些内部资源或者状态(因为源对象将会被丢弃)。
移动语义对模板的设计有重要影响,在泛型代码中也引入了一些特殊的规则来支持移动语义。本章将会介绍移动语义这一特性。
完美转发
假设希望实现的泛型代码可以将被传递参数的基本特性转发出去:
- 可变对象被转发之后依然可变
- Const 对象被转发之后依然是 const 的
- 可移动对象(可以从中窃取资源的对象)被转发之后依然是可移动的。
不适用模板的时候,达成这一目的需要对这三种情况分别编程,如下所示是将调用f()时传递的参数转发给g():
1 |
|
注意第三个f()函数:它需要用 std::move() 来处理其参数,因为参数的移动语义不会被一起传递。虽然第三个 f()中的 val 被声明成右值引用,但是当其在 f()内部被使用时,它依然是一个非常量左值(参考附录 B),其行为也将 和第一个f()中的情况一样。因此如果不使用std::move()的话, 在第三个f()中调用的将是g(X&) 而不是 g(X&&)。
如果尝试泛型代码统一以上三种情况,会出现以下问题:
1 | template<typename T> |
这个模板只对前两种情况有效,对第三种用于可移动对象的情况无效。
基于这一原因,C++11 引入了特殊的规则对参数进行完美转发(perfectforwarding)。实现 这一目的的惯用方法如下:
1 | template<typename T> |
不要以为模板参数 T 的 T&&和具体类型 X 的 X&&是一样的。虽然语法上看上去类似,但是它们适用于不同的规则:
- 具体类型 X 的 X&&声明了一个右值引用参数。只能被绑定到一个可移动对象上(一个 prvalue,比如临时对象,一个 xvalue,比如通过 std::move()传递的参数)。它的值总是可变的,而且总是可以被“窃取”。
- 模板参数 T 的 T&&声明了一个转发引用(亦称万能引用)。可以被绑定到可变、不可变(比如 const)或者可移动对象上。在函数内部这个参数也可以是可变、不可变或者指向一个可以被窃取内部数据的值。
1 |
|
通过 std::enable_if<>禁用模板
从 C++11 开始,通过 C++标准库提供的辅助模板 std::enable_if<>,可以在某些编译期条件下忽略掉函数模版
比如,如果函数模板 foo<>的定义如下:
1 | template<typename T> |
也就是说 std::enable_if<>是一种类型萃取(typetrait),它会根据一个作为其(第一个)模板参数的编译期表达式决定其行为:
- 如果这个表达式结果为 true,它的 type 成员会返回一个类型:
- 如果没有第二个模板参数,返回类型是 void。
- 否则,返回类型是其第二个参数的类型。
- 如果表达式结果 false,则其成员类型是未定义的。根据模板的一个叫做 SFINAE (substitutefailureisnotanerror,替换失败不是错误,将在 8.4 节进行介绍)的规则, 这会导致包含 std::enable_if<>表达式的函数模板被忽略掉。
由于从 C++14 开始所有的模板萃取(typetraits)都返回一个类型,因此可以使用一个与之 对应的别名模板 std::enable_if_t<>,这样就可以省略掉 template 和::type 了。如下:
1 | template<typename T> |