前面提到过,重载通用引用函数,得到的结果往往不是预期的,那么就应该以其它的方式来替代这种迷惑的方式。
以其它的名字命名重载函数
既然对通用引用函数的重载会导致非预期的调用,那么就将那些重载函数以其它名字命名,就绕过了这个坑。 > 对于常年写 c 的人来说,这个方法是最自然而然地。
函数参数使用const T&
当重载函数不会改变实参内容时,可以使用const T&
来做参数限定,以替代通用引用。
>
这种方式在遇到右值时,也会以拷贝的方式进行创建新对象,虽然效率不高。但是很多时候可以让代码更易于理解。
参数以值传递,并且辅以std::move
1 | class Person { |
这样既不会造成重载调用的非预期,还可以使用移动语义提高效率。
使用标记分发
简单的说就是在通用引用函数中使用标记分发,使得编译器在编译时就可以确定调用匹配:
1 | //当 std::false_type 满足后,此函数才会被调用 |
对通用引用进行限制
通过std::enable_if
这种黑魔法,可以做到直接在通用引用函数上做限制,比如下面就限定,当实参类型不是Person
时,才使用通用引用的构造函数。否则就使用拷贝构造、拷贝赋值这类构造函数。
1 | class Person { |
std::decay
用于将推导出的 T 中的引用、const
、volatile
限定给去除。以表示带引用、const
、volatile
的Person
对象都是一样的。 >typename std::decay<T>::type
之后得到的就是去除约束后的T
。std::is_same
是用于比较,传入的类型是否与Person
一致std::enable_if
则是当其条件为真时,才会使能该函数
以上的所有步骤都是在编译期完成的,这就是模板元编程的魅力所在。
对于有继承的情况下,情况还要复杂一点: 1
2
3
4
5
6
7
8
9
10class SpecialPerson: public Person {
public:
SpecialPerson(const SpecialPerson& rhs) // copy ctor; calls
: Person(rhs) // base class
{ } // forwarding ctor!
SpecialPerson(SpecialPerson&& rhs) // move ctor; calls
: Person(std::move(rhs)) // base class
{ } // forwarding ctor!
//…
};
上面这种情况下,基类Person
被传入的是子类,所以原来的那种写法也会导致通用引用版本的构造函数被调用。
这个时候,需要使用std::is_base_of
来替换std::is_same
:
1
2
3
4
5
6
7
8
9
10
11
12
13class Person {
public:
template<
typename T,
typename = typename std::enable_if<
!std::is_base_of<Person,
typename std::decay<T>::type
>::value
>::type
>
explicit Person(T&& n);
//…
};
使用std::is_base_of
之后,无论T
是基类还是子类,都得到结果为true
,这样就可以避免上面的问题了。