上一篇避免依万能引用型别进行重载讲过使用万能引用型别进行重载可能会存在的问题, 这一篇我们就来讲讲如何规避这些问题:
一、舍弃重载
既然万能引用重载会有问题,那么我们就直接多写几个函数,命名成不同名字,杜绝重载就可以。
二、传递const T&型别的形参
使用传递左值常量引用型别来替代万能引用型别,这样在就可以限制该函数接受的参数了。
三、传值
如果你确定你的实参的复制操作将无可避免,可以使用传值方式来替代万能引用。
四、标签分派
上面几种都不支持完美转发,不是很满足效率提升的要求,所以如果为了效率,坚持使用万能引用的话,我们该做些什么呢?
为万能引用型别的重载函数加上标签,当满足标签要求时,才会调用该函数。
在此之前我们先熟悉一下几个比较这样的模板类:
std::is_integral模板:用于检查给定类型是否为整数。它返回一个布尔值。例如:std::is_integral<long>(),返回值为1
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修饰等就关闭模板。具体原理欢迎自行百度或留言。