从 C 向 C++ 进阶系列导航
1. 多参数类模版
类模板支持多个泛型参数,使用方式与函数模板基本一致。
- 实验:
template <typename T1, typename T2>
class Operator
{
public:
T2 add(T1 a, T2 b);
T1 minus(T1 a, T1 b);
};
template <typename T1, typename T2>
T2 Operator<T1,T2>::add(T1 a, T2 b)
{
return a + b;
}
template <typename T1, typename T2>
T1 Operator<T1,T2>::minus(T1 a, T1 b)
{
return a - b;
}
int main()
{
Operator<int,float> obj;
cout << "obj.add(1, 2) = " << obj.add(1, 2.2) << endl; // obj.add(1, 2) = 3.2
cout << "obj.minus(4, 5) = " << obj.minus(4, 5) << endl; // obj.minus(4, 5) = -1
return 0;
}
2. 模版特化
模板特化,指的是模板的特殊形态,是为了解决不同数据类型时需执行不同的动作而提出的概念。举个例子,当我们定义了一个类模板,其提供了一个有数据比较功能的成员函数,如果不使用模板特化,则直接比较整形与整形、浮点型与浮点型等数据是没问题的,但对于字符串类型是明显不能直接比较的。因此,对于比较字符串的功能,可以使用模板特化。
模板特化与重定义有什么区别呢?重定义和特化是不同的,进行重定义时是:会产生两个不同的类或函数,使用的时候需要考虑如何选择的问题。而模板特化后还是属于同一个模板的,特化只是模板的内在实现时需要不同的形态,但对外仍然以统一的形态满足不同数据类型的相同的功能需求。
3. 函数模版特化
函数模板只支持完全特化,所谓的完全特化指的是只有在函数调用时数据类型与函数模板特化的数据类型完全匹配上时才能调用到函数模板的特化,否则仍然会调用到普通的函数模板。
在工程中,当需要对函数模板进行重载时,优先使用模板特化,特化仍然不能满足需求时再考虑进行函数重载。
- 实验:
template <typename T1, typename T2>
void Compare(T1 val1, T2 val2)
{
if(val1 == val2)
{
cout << val1 << " is equal to " << val2 << endl;
}
else
{
cout << val1 << " is not equal to " << val2 << endl;
}
}
/* 函数的完全特化 */
template <>
void Compare<string, string>(string val1, string val2)
{
if(!val1.compare(val2))
{
cout << "\"" << val1 << "\"" << " is equal to " << "\"" << val2 << "\"" << endl;
}
else
{
cout << "\"" << val1 << "\"" << " is not equal to " << "\"" << val2 << "\"" << endl;
}
}
int main()
{
Compare<int,int>(1,1); // 1 is equal to 1
Compare<string,string>("123","456"); // "123" is not equal to "456"
Compare<float,int>(3.14,3); // 3.14 is not equal to 3
// Compare<string,int>("12",12); // error: no match for ‘operator==’ (operand types are ‘std::__cxx11::basic_string<char>’ and ‘int’)
return 0;
}
当特化的类型为模板类时,模板的特化方式有以下三种:
/* TYPE 1 */
template <typename T>
type funname(classname<T> obj)
{
...
}
/* TYPE 2 */
template <typename T, template<typename> typename classname>
type funname(classname<T> obj)
{
...
}
/* TYPE 3 */
template <template<typename> typename classname>
type funname(classname<type> obj)
{
...
}
方式 1 的特化方式实际上仍然是普通的模式定义方法,只是类型 T 所泛指的不是形参的数据类型,而是形参模板类中的泛指类型。此时,当函数调用的传参为非所指定的模板类或其派生模板类时,将无法调用该特化函数。
方式 2 为真正的函数模板对模板类类型的特化,其泛指类型 T 仍然是形参模板类中的泛指类型,同时特化了形参的模板类类型。此时,即便函数调用的传参为指定的模板类的派生模板类时,也无法调用该特化函数。即此时多态的特性也将失效,传参类类型与函数的形参类类型必须强制一一对应。
方式 3 为方式 2 的基础上不使用泛指类型 T,只特化形参的模板类类型,并指明模板类中的数据类型。该方式与函数重载的区别是,当函数调用的传参为指定的模板类的派生模板类时,也无法调用该特化函数,而重载的函数能够接收形参类类型及其派生类类型。
- 实验:
template <typename T>
class Base
{
public:
Base(){}
~Base(){}
};
template <typename T, int N>
class Derived : public Base<T>
{
T array[N];
public:
Derived(){}
~Derived(){}
};
template <typename T>
void fun1(Base<T>& obj)
{
cout << "template <typename T> void fun1(Base<T>& obj)" << endl;
}
template <typename T, template<typename> typename Base>
void fun2(Base<T>& obj)
{
cout << "template <typename T, template<typename> typename Base> void fun2(Base<T>& obj)" << endl;
}
template <template<typename> typename Base>
void fun3(Base<int>& obj)
{
cout << "template <template<typename> typename Base> void fun3(Base<int>& obj)" << endl;
}
void fun4(Base<int>& obj)
{
cout << "void fun4(Base<int>& obj)" << endl;
}
int main(void)
{
Base<int> frither;
Derived<int, 10> son;
fun1(frither); // print: template <typename T> void fun1(Base<T>& obj)
fun1(son); // print: template <typename T> void fun1(Base<T>& obj)
fun2(frither); // print: template <typename T, template<typename> typename Base> void fun2(Base<T>& obj)
// fun2(son); // error: no matching function for call to ‘fun2(Derived<int, 10>&)’
fun3(frither); // print: template <template<typename> typename Base> void fun3(Base<int>& obj)
// fun3(son); // error: no matching function for call to ‘fun3(Derived<int, 10>&)’
fun4(frither); // void fun4(Base<int>& obj)
fun4(son); // void fun4(Base<int>& obj)
}
4. 类模板特化
类模板支持部分特化与完全特化。完全特化与函数模板的完全特化同理,而部分特化指的是类模板同时存在泛型与指定的数据类型,在定义类对象时只需部分数据类型与特化中的指定的数据类型匹配上,便可调用到特化的类模板。
当定义类对象时指定的数据类型即满足部分特化的类模板也满足完全特化的类模板,会优先使用完全特化的类模板。
- 实验:
/* 普通类模板 */
template <typename T1, typename T2>
class Test
{
public:
void Compare(T1 val1, T2 val2);
};
template <typename T1, typename T2>
void Test<T1,T2>::Compare(T1 val1, T2 val2)
{
if(val1 == val2)
{
cout << val1 << " is equal to " << val2 << endl;
}
else
{
cout << val1 << " is not equal to " << val2 << endl;
}
}
/* 类模板部分特化 */
template <typename T>
class Test<string,T>
{
public:
void Compare(string val1, T val2);
};
template <typename T>
void Test<string,T>::Compare(string val1, T val2)
{
cout << "Compare error! Don't use string compare to other types!" << endl;
}
/* 类模板完全特化 */
template <>
class Test<string,string>
{
public:
void Compare(string val1, string val2);
};
// template <> // error: template-id ‘Compare<>’ for ‘void Test<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >::Compare(std::__cxx11::string, std::__cxx11::string)’ does not match any template declaration
void Test<string,string>::Compare(string val1, string val2)
{
if(!val1.compare(val2))
{
cout << "\"" << val1 << "\"" << " is equal to " << "\"" << val2 << "\"" << endl;
}
else
{
cout << "\"" << val1 << "\"" << " is not equal to " << "\"" << val2 << "\"" << endl;
}
}
int main()
{
Test<int,int> obj_A;
obj_A.Compare(10,10); // 10 is equal to 10
obj_A.Compare(10,15); // 10 is not equal to 15
Test<string,int> obj_B;
obj_B.Compare("100",100); // Compare error! Don't use string compare to other types!
Test<string,float> obj_C;
obj_C.Compare("100",12.3); // Compare error! Don't use string compare to other types!
Test<int,string> obj_D;
// obj_D.Compare(100,"100"); // error: no match for ‘operator==’ (operand types are ‘int’ and ‘std::__cxx11::basic_string<char>’)
Test<string,string> obj_E;
obj_E.Compare("123","456"); // "123" is not equal to "456"
obj_E.Compare("123","123"); // "123" is equal to "123"
return 0;
}
需要注意的是,在类模板的完全特化时,当成员函数在类外实现,是不需要在函数前声明 template <>
的,声明 template 语句的含义就是告诉编译器接下来会使用泛型编程,但在完全特化中并没有泛型。