上一篇避免依万能引用型别进行重载讲过使用万能引用型别进行重载可能会存在的问题, 这一篇我们就来讲讲如何规避这些问题:
一、舍弃重载
既然万能引用重载会有问题,那么我们就直接多写几个函数,命名成不同名字,舍弃重载就可以了,但是舍弃重载并非长久之计。
二、传递const T&型别的形参
使用传递左值常量引用型别来替代万能引用型别,但这样对于右值依然采用复制方式进行传参,效率会有所下降。
三、传值
如果你确定你的实参的复制操作将无可避免,可以使用传值方式来替代万能引用。
四、标签分派
为万能引用型别的重载函数加上标签,根据标签的满足情况,强制重载决议按照完美想要的方向推进。在下面的代码中,型别std::false_type以及std::true_type就是所谓标签,这些形参没有名子。
在此之前我们先熟悉一下几个比较这样的模板类:
- std::is_integral模板:用于检查给定类型是否为整数。它返回一个布尔值。例如:std::is_integral<long>(),返回值为true
- std::remove_reference<T>模板:用于去除某个类型的引用部分,返回的是去除引用后的类型。例如:remove_reference<short&>::type
- std::false_type、std::true_type:分别表示false和true的型别。
所以对上一篇的代码我们可以改写为这样:
#include <memory>
#include <iostream>
#include <set>
using namespace std;
//万能引用型别重载
template <class T>
void MaKeZiYuan(T&& t, std::false_type) {
cout << "MaKeZiYuan(T&& t)" << endl;
};
//参数为int
void MaKeZiYuan(int t,std::true_type) {
cout << "MaKeZiYuan(int t)" << endl;;
};
template <class T>
void BaseMaKeZiYuan(T&& t) {
//根据标签确定调用的函数是哪一个
MaKeZiYuan(std::forward<T>(t), std::is_integral<typename std::remove_reference<T>::type>());
};
int main()
{
BaseMaKeZiYuan("123"); //参数为char*,匹配万能引用型别
BaseMaKeZiYuan(456); //参数为整型常量(int),匹配int型别
int j = 456;
BaseMaKeZiYuan(j); //参数为int,匹配int型别
short i = 1;
BaseMaKeZiYuan(i); //参数为short,匹配int型别
short& k = i;
BaseMaKeZiYuan(k); //参数为long,匹配int型别
system("pause");
}
输出如下:
五、对接受万能引用的模板施加限制
上面第四条的规避手段只适应于那些自己编写的重载函数,如果是编译器自己生成的函数,我们将束手无策。
这里再介绍几种std里面的几个关键字:
- std::enable_if:如果条件满足,将强制编译停用模板,当作模板不存在一样。
- std::is_same<type,T>::value:判断type与T的类型是否一致,一模一样才返回真
- std::decay<T>::type:移除T的引用及const、volatile修饰词。
- std::is_base_of<T1,T2>::value:判断T1与T2是否继承与一个基类,认为自身是继承与自身的。
我们先来看一个问题代码:
#include <iostream>
using namespace std;
class Person {
public:
// 万能引用型别构造函数
template <class T>
Person(T&& p) {
cout << "Person(T&& p)" << endl;
}
// 接受一个int类型的构造函数
Person(int i) {
cout << "Person(int i)" << endl;
}
};
int main()
{
Person p(0); // 调用int型构造函数
Person p1(p); // 没有调用默认复制构造函数,而是调用了万能引用函数,
// 因为默认构造函数接受的参数是const类型的,模板匹配更佳
system("pause");
}
执行结果如下:
很明显这不符合我们的预期,除了修改实参外,我们到底要如何修改,让他调用我们的默认复制构造函数呢?
很明显,让万能引用不接受Person类型的参数即可。实现代码如下:
#include <iostream>
using namespace std;
class Person {
public:
//唯一修改,就是判断如果实参是Person及其子类的类型,包括引用或const、volatile修饰等就关闭模板
template <class T,typename = std::enable_if_t<!std::is_base_of<Person,std::decay_t<T>>::value>>
Person(T&& p) {
cout << "Person(T&& p)" << endl;
}
Person(int i) {
cout << "Person(int i)" << endl;
}
};
int main()
{
Person p(0);
Person p1(p);
system("pause");
}
唯一修改,就是判断如果实参是Person及其子类的类型,包括引用或const、volatile修饰等就关闭模板。