目录
一、开发者需要对模板参数负责
1.1 为您模板参数提供匹配的操作
在进行模板设计时,函数模板或类模板一般只做模板参数(typename T)无关的操作为主,但是也不见得就不会关联模板参数自身的操作,尤其是在一些自定义的数据类型作为模板参数传入时。
看下面这段代码,在v1<v2时,就调用了标准库里的operator<操作符比较函数,由于typename T是std::string,在string类本身是支持了值比较的,因此调用正常。
template <typename T>
inline T mymin(const T &v1, const T &v2)
{
return (v1<v2)?v1:v2;
};
//会调用bool operator<(const T& obj1, const T& obj2)操作符函数"<"
mymin<std::string>("guangzhou","shenzhen");
那现在再来调整一下代码,自定义数据类型DataTest,其包含两个int型成员变量,现在将该类型作为模板参数传递给mymin函数时,会编译异常,提示并不支持operator<,虽然int型支持operator<,但两个int型的组合在一起就需要模板使用者来为模板参数(typename T)操作行为负责:
class DataTest
{
public:
DataTest(const int &id_d,const int &id_p)
: id_domain(id_d),id_point(id_p)
{
};
~DataTest()
{
};
int id_domain;
int id_point;
};
//
DataTest adt(3,4),bdt(3,6);
mymin<DataTest>(adt,bdt); //error, no match for 'operator<'
为此,我们就需要给DataTest定义其operator<操作支持了
class DataTest
{
public:
DataTest(const int &id_d,const int &id_p)
: id_domain(id_d),id_point(id_p)
{
};
~DataTest()
{
};
int id_domain;
int id_point;
};
inline bool operator<(const DataTest& obj1, const DataTest& obj2)
{
if(obj1.id_domain<obj2.id_domain)
return true;
if(obj1.id_domain==obj2.id_domain&&obj1.id_point<obj2.id_point)
return true;
return false;
};
//
DataTest adt(3,4),bdt(3,6);
mymin<DataTest>(adt,bdt); //OK, find 'operator<' success
1.2 模板嵌套-类模板作为模板参数
再进一步深化一下代码设计,如果给函数模板mymin传递一个类模板A_Test呢,同样地为A_Test提供operator<操作支持,又会怎样。
template <typename T1, typename T2>
class A_Test
{
public:
A_Test(const T1 &id_d,const T2 &id_p)
: id_domain(id_d),id_point(id_p)
{
};
~A_Test()
{
};
T1 getDomainID() const
{
return id_domain;
};
T2 getPointID() const
{
return id_point;
};
private:
T1 id_domain;
T2 id_point;
};
template <typename T1, typename T2>
inline bool operator<(const A_Test<T1,T2>& obj1, const A_Test<T1,T2>& obj2)
{
if(obj1.getDomainID()<obj2.getDomainID())
return true;
if(obj1.getPointID()==obj2.getPointID()&&obj1.getPointID()<obj2.getPointID())
return true;
return false;
};
//
A_Test<int,float> a(3,4.2),b(2,3.8);
mymin<A_Test<int,float> >(a,b); //OK,没什么区别,只是换成特例化类模板
1.3 友元模板
在外部由于operator<是放置在类声明体外声明与定义的,它就不能直接使用类模板内的成员变量,就需要为它提供额外的访问函数getDomainID、getPointID,这样转一手的操作显然不符合inline的诉求,其实在标准库里,通常类模板会operator<声明友元函数,函数或类被声明为友元后,在类模板内就是真不把自身当外人了,甚至是类模板的儿子(派生类)都比不上。
为了区别前面一种方法,我们重新定义一个函数模板mymax,在类模板会operator>声明友元函数,看下列代码:
template <typename T1, typename T2>
class A_Test
{
public:
A_Test(const T1 &id_d,const T2 &id_p)
: id_domain(id_d),id_point(id_p)
{
};
~A_Test()
{
};
//template <typename T> friend bool operator>(const T& obj1, const T& obj2); //why is eroor
template <typename T3, typename T4>
friend bool operator>(const A_Test<T3,T4>& obj1, const A_Test<T3,T4>& obj2);
private:
T1 id_domain;
T2 id_point;
};
//
template <typename T1, typename T2>
inline bool operator>(const A_Test<T1,T2>& obj1, const A_Test<T1,T2>& obj2)
{
if(obj1.id_domain<obj2.id_domain) //直接访问私有成员不含糊
return true;
if(obj1.id_domain==obj2.id_domain&&obj1.id_point<obj2.id_point) //直接访问私有成员不含糊
return true;
return false;
};
//
template <typename T>
inline T mymax(const T &v1, const T &v2)
{
return (v1>v2)?v1:v2;
};
//
A_Test<int,float> a(2,4.2),b(2,3.8);
mymin<A_Test<int,float> >(a,b); //OK
A_Test<std::string,std::string> a_s("guangzhou","huangpu"),b_s("shenzhen","baoan");
if(a_s<b_s); //OK
if(a_s>b_s); //OK,两者都可以实现,不看operator内部实现的话
1.4 节省模板的配套操作
当然在实际设计中,我们最好还是避免同一模板的多个实例化中隐含的编译进开销:
template <typename T1, typename T2>
inline bool operator>(const A_Test<T1,T2>& obj1, const A_Test<T1,T2>& obj2)
{
if(obj1.id_domain<obj2.id_domain)
return true;
if(obj1.id_domain==obj2.id_domain&&obj1.id_point<obj2.id_point)
return true;
return false;
};
template <typename T>
inline T mymax(const T &v1, const T &v2)
{
return (v1>v2)?v1:v2;
};
//
template <typename T>
inline T mymin(const T &v1, const T &v2)
{
return (v1>v2)?v2:v1; //这样同样也能达成效果
};
1.5 有度地规划类模板操作符
在设计类模板时需要慎重并认真地思考如何支撑一些通用的操作符调用,因为往往类模板设计者和使用者往往不会是同一个作者,那么使用者对待类模板时,会习惯性地把普通类型的调用习惯用到类模板上来设计其代码时,缺发现其实并不支持。例如,对于一个常规数据类型,输出显示是没有问题的,但是对于自定义的类模板,那就要想到使用者会有这样的用法习惯,给与到支持。
std::cout << mymin<std::string>("guangzhou","shenzhen") << std::endl;
A_Test<int,float> a(3,4.2),b(2,3.8);
std::cout << mymin<A_Test<int,float> >(a,b) << std::endl; //error
那么这些常用的一些操作函数,还是需要通盘考虑和设计的,当然使用者如果谨慎,他也应该明确知道模板参数行为需要使用者本身去把控。
template <typename T1, typename T2>
class A_Test
{
public:
A_Test(const T1 &id_d,const T2 &id_p)
: id_domain(id_d),id_point(id_p)
{
};
~A_Test()
{
};
template <typename T3, typename T4>
friend std::ostream &operator<<(std::ostream &os, const A_Test<T3,T4>& obj);
private:
T1 id_domain;
T2 id_point;
};
//
template <typename T1, typename T2>
inline std::ostream &operator<<(std::ostream &os, const A_Test<T1,T2>& obj)
{
os << "(";
os << obj.id_domain << "," << obj.id_point;
os <<")";
return os;
};
//
A_Test<int,float> a(2,4.2),b(2,3.8);
std::cout << mymin<A_Test<int,float> >(a,b) << std::endl; //OK
//
A_Test<std::string,std::string> a_s("guangzhou","huangpu"),b_s("shenzhen","baoan");
std::cout << mymin<A_Test<std::string,std::string> >(a_s,b_s) << std::endl; //OK
std::cout << mymax<A_Test<std::string,std::string> >(a_s,b_s) << std::endl; //OK
1.7 按需增加必要支持
当然如何把控是模板设计的度,都是设计者及使用者的考验,例如,想更进一步使用上述的模板函数和类模板:
A_Test<DataTest,DataTest> ad_s(DataTest(3,4),DataTest(4,1)),bd_s(DataTest(2,4),DataTest(3,1));
std::cout << mymin<A_Test<DataTest,DataTest> >(ad_s,bd_s) << std::endl;
std::cout << mymax<A_Test<DataTest,DataTest> >(ad_s,bd_s) << std::endl;
显然,针对DataTest,我们需要重新去设计其在上层调用涉及到的<、>、== 这三个操作符函数声明定义后,才能被A_Test类模板和mymin、mymax函数模板所识别使用。
inline bool operator<(const DataTest& obj1, const DataTest& obj2)
{
if(obj1.id_domain<obj2.id_domain)
return true;
if(obj1.id_domain==obj2.id_domain&&obj1.id_point<obj2.id_point)
return true;
return false;
};
inline bool operator>(const DataTest& obj1, const DataTest& obj2)
{
if(obj1.id_domain>obj2.id_domain)
return true;
if(obj1.id_domain==obj2.id_domain&&obj1.id_point>obj2.id_point)
return true;
return false;
};
inline bool operator==(const DataTest& obj1, const DataTest& obj2)
{
if(obj1.id_domain!=obj2.id_domain)
return false;
if(obj1.id_point!=obj2.id_point)
return false;
return true;
};
二、模板隐藏的那些事
2.1 成员函数或成员变量的模板参数
类模板内的成员函数或成员变量类型都可以具有自己的模板参数。
template <typename T>
class OUT_Class
{
public:
void myfun1(const T& obj); //和外围模板使用同一个模板参数
template <typename T1>
void myfun2(const T1& obj); //自定义了自己的模板参数
};
template <typename T>
void OUT_Class<T>::myfun1(const T& obj)
{
std::cout << obj << std::endl;
};
template <typename T> //主类的模板参数
template <typename T1> //子类的模板参数
void OUT_Class<T>::myfun2(const T1& obj)
{
std::cout << obj << std::endl;
};
OUT_Class<int> o_a;
o_a.myfun1(10);
o_a.myfun2<double>(10.5);
模板在外部定义时,具有多个模板参数语句template <typename ***>,一个语句用于自身,另一个语句用于外围模板,语句测顺序是从外围到内部的。
类模板内的函数模板 或类模板可以在主类模板内部定义,也可以放置外部定义。若是函数模板在类内部定义,则是一个显式内联函数。
//
template <typename T>
class OUT_Class
{
pbulic:
template <typename T1>
class INT_Class1 //内部直接定义
{
public:
T1 val;
};
template <typename T1>
class INT_Class2; //外部定义
};
//
template <typename T>
template <typename T1>
class OUT_Class<T>::INT_Class2
{
public:
T1 val;
};
//
OUT_Class<int>::INT_Class1<float> o_a_i1;
o_a_i1.val = 11.4;
OUT_Class<int>::INT_Class2<float> o_a_i2;
o_a_i2.val = 12.4;
2.2 联合(Union)模板
模板设计还允许联合(Union)模板的存在:
template <typename T>
union Chunk
{
T obj;
unsigned char bytes[sizeof(T)];
};
//
union Chunk<int> uobj;
uobj.bytes[0] = 0xff;
std::cout << uobj.obj << std::endl;
2.3 模板的虚函数问题
类模板内的成员函数模板不能声明为为虚函数,因为虚函数调用机制实现使用了一个大小固定的表,每个虚函数都对应表的一个入口,但成员函数模板在构建虚函数表之前是无法确定的。当然,类模板内的普通成员函数设置为虚函数是没问题的。
template <typename T>
class OUT_Class
{
public:
//
virtual ~V_Class() //OK
{
};
template <typename T>
virtual void copy(const T& obj) //error
{
};
};
2.4 模板的编译链接影响
目前类模板和普通类一样,是能和一个实体共享一个名称的,虽然要尽量避免这种情况。
template <typename T1, typename T2>
class A_Test
{
};
int A_Test; //OK
大多C++编译器都不能支持其具有C连接,因此也不建议采用extern来指定编译。并由于模板是采用外部链接的,也不能在函数内部声明模板,因为编译器无法在链接时确定到该定义。另外也不允许在模板内将模板参数再作为类名、结构名等修饰名称。
extern "C" template <typename T> class C_Test{}; //error,不支持
extern "C++" template <typename T> void Func_Test(){};//可行,但多余
//
void test_in(const int& obj)
{
template <typename T> //不能在函数内部声明模板
class AC{};
//..
};
//
template <typename T>
class AClass
{
class T *inc; //error
friend class T; //error
};
2.5 模板的模板参数
前面讲述到模板可以进行嵌套,其实,模板参数也能进行嵌套,即模板的模板参数,它的用法和类模板的用法类似:
template<typename T> class CX
{
public:
T val;
};
template <template<typename T> class CX>
void func(CX<int>& obj)
{
std::cout << obj.val << std::endl;
};
//
CX<int> cx_i;
cx_i.val = 20;
func(cx_i);
同时模板的模板参数也支持缺省实参模板
template<typename T=int>
class CX
{
public:
T val;
};
//template <template<typename T> class CX> //class 替换成 struct union 是错误的
template <template<typename T> typename CX>
//void func(CX<T> &obj) //error,模板的模板参数的参数只能被自身其他参数的声明使用
void func(CX<int> &obj)
{
std::cout << obj.val << std::endl;
};
//或者这样更好理解
template <typename T,template<typename T1> typename CX>
void func2(CX<T> &obj)
{
std::cout << obj.val << std::endl;
};
//
CX<> cx_def;
cx_def.val = 21;
func(cx_def);
//
func2(cx_def);
2.6 模板实参演绎事项
在篇一中,就讲述到,模板在使用时,可以显式指定模板实参,或者不指定,交给编译器去进行实参演绎,因此,最好是吧那些无法演绎的试产放在模板参数列表前面,从而显式指定这些实参,把支持实参演绎的放在后面。
template<typename T1, typename T2>
//inline T2 im_cast(const T1& obj) //尝试一下这样设计呢
inline T1 im_cast(const T2& obj)
{
return obj;
};
//
double val_ = im_cast<double>(-30);
std::cout << val_ << std::endl;
2.7 模板参数是函数模板
对于模板的嵌套,还需要说明的是,函数模板可以作为函数模板的模板参数:
//函数模板嵌套
template<typename Func, typename T>
void doSomething(Func fp,const T obj)
{
fp(obj);
};
template<typename T>
void doit(const T obj)
{
std::cout << obj << std::endl;
};
//
doSomething(doit<int>,3);
doSomething(&doit<int>,4);
2.8 模板参数是函数指针
另外函数模板也可以作为函数指针来使用,在具体使用函数指针时,指定模板实参即可
//
template <typename T>
void func3(const T &obj)
{
std::cout << obj << std::endl;
};
//
void (*pfunc3)(const int &obj);
pfunc3 = func3<int>;
pfunc3(15);
//
typedef void (*PFunc)(const float &obj);
PFunc pf = func3<float>;
pf(12.5);
(*pf)(13.5);
2.9 命名空间与模板
编译器在模板调用时,会依据上下文进行名称查找,其查找是有作用域限制的,如果模板需要调用另一个命名空间namespace定义数据类型,就需要明确指出模板参数所包含的命名空间。
//
namespace pyfree{
class PVal
{
public:
int val;
};
std::ostream &operator<<(std::ostream &os, const PVal& obj)
{
os << "(";
os << obj.val;
os <<")";
return os;
};
};
//
template <typename T>
void func3(const T &obj)
{
std::cout << obj << std::endl;
};
//
pyfree::PVal pval;
pval.val = 33;
func3<pyfree::PVal>(pval);
总之。普通函数及类需要注意的问题,函数模板及类模板使用时一样要注意,另外由于模板的特殊性,还会延展出其很多新的问题点,当然也会对编程带来全新的编程架构和编码风格。
三、源码补充
3.1 编译
测试代码包含两个源文件template_test.h和test.cpp,通过g++ test -o test.exe编译运行测试:
3.2 源代码
template_test.h
#ifndef _TEMPLATE_TEST_H_
#define _TEMPLATE_TEST_H_
#include <ostream>
#include <iostream>
class DataTest
{
public:
DataTest(const int &id_d,const int &id_p)
: id_domain(id_d),id_point(id_p)
{
};
~DataTest()
{
};
int id_domain;
int id_point;
};
inline bool operator<(const DataTest& obj1, const DataTest& obj2)
{
if(obj1.id_domain<obj2.id_domain)
return true;
if(obj1.id_domain==obj2.id_domain&&obj1.id_point<obj2.id_point)
return true;
return false;
};
inline bool operator>(const DataTest& obj1, const DataTest& obj2)
{
if(obj1.id_domain>obj2.id_domain)
return true;
if(obj1.id_domain==obj2.id_domain&&obj1.id_point>obj2.id_point)
return true;
return false;
};
inline bool operator==(const DataTest& obj1, const DataTest& obj2)
{
if(obj1.id_domain!=obj2.id_domain)
return false;
if(obj1.id_point!=obj2.id_point)
return false;
return true;
};
inline std::ostream &operator<<(std::ostream &os, const DataTest& obj)
{
os << "(";
os << obj.id_domain << "," << obj.id_point;
os <<")";
return os;
};
template <typename T1, typename T2>
class A_Test
{
public:
A_Test(const T1 &id_d,const T2 &id_p)
: id_domain(id_d),id_point(id_p)
{
};
~A_Test()
{
};
T1 getDomainID() const
{
return id_domain;
};
T2 getPointID() const
{
return id_point;
};
template <typename T3, typename T4>
friend std::ostream &operator<<(std::ostream &os, const A_Test<T3,T4>& obj);
//template <typename T> friend bool operator>(const T& obj1, const T& obj2); //why is eroor
template <typename T3, typename T4>
friend bool operator>(const A_Test<T3,T4>& obj1, const A_Test<T3,T4>& obj2);
private:
T1 id_domain;
T2 id_point;
};
template <typename T1, typename T2>
inline std::ostream &operator<<(std::ostream &os, const A_Test<T1,T2>& obj)
{
os << "(";
os << obj.id_domain << "," << obj.id_point;
os <<")";
return os;
};
template <typename T1, typename T2>
inline bool operator<(const A_Test<T1,T2>& obj1, const A_Test<T1,T2>& obj2)
{
if(obj1.getDomainID()<obj2.getDomainID()) return true;
if(obj1.getPointID()==obj2.getPointID()&&obj1.getPointID()<obj2.getPointID()) return true;
return false;
};
template <typename T1, typename T2>
inline bool operator>(const A_Test<T1,T2>& obj1, const A_Test<T1,T2>& obj2)
{
if(obj1.id_domain>obj2.id_domain) return true;
if(obj1.id_domain==obj2.id_domain&&obj1.id_point>obj2.id_point) return true;
return false;
};
template <typename T>
inline T mymin(const T &v1, const T &v2)
{
return (v1<v2)?v1:v2;
};
template <typename T>
inline T mymax(const T &v1, const T &v2)
{
return (v1>v2)?v1:v2;
};
//
template <typename T>
class OUT_Class
{
public:
//
virtual ~OUT_Class()
{
};
/*
template <typename T>
virtual void copy(const T& obj)
{
};
*/
//
void myfun1(const T& obj);
template <typename T1>
void myfun2(const T1& obj);
//
template <typename T1>
class INT_Class1 //内部直接定义
{
public:
T1 val;
};
template <typename T1>
class INT_Class2; //外部定义
};
template <typename T>
void OUT_Class<T>::myfun1(const T& obj)
{
std::cout << obj << std::endl;
};
template <typename T>
template <typename T1>
void OUT_Class<T>::myfun2(const T1& obj)
{
std::cout << obj << std::endl;
};
template <typename T>
template <typename T1>
class OUT_Class<T>::INT_Class2
{
public:
T1 val;
};
template <typename T>
union Chunk
{
T obj;
unsigned char bytes[sizeof(T)];
};
//extern "C" template <typename T> class C_Test{}; //error,不支持
extern "C++" template <typename T> void Func_Test(){};
/*
void test_in(const int& obj)
{
template <typename T> //不能在函数内部声明模板
class AC{};
//..
};
template <typename T>
class AClass
{
class T *inc; //error
friend class T; //error
};
*/
template<typename T=int>
class CX
{
public:
T val;
};
//template <template<typename T> class CX> //class 替换成 struct union 是错误的
template <template<typename T> typename CX>
//void func(CX<T> &obj) //error,模板的模板参数的参数只能被自身其他参数的声明使用
void func(CX<int> &obj)
{
std::cout << obj.val << std::endl;
};
template <typename T,template<typename T1> typename CX>
void func2(CX<T> &obj)
{
std::cout << obj.val << std::endl;
};
//
template<typename T1, typename T2>
inline T1 im_cast(const T2& obj)
{
return obj;
};
//
template<typename Func, typename T>
void doSomething(Func fp,const T obj)
{
fp(obj);
};
template<typename T>
void doit(const T obj)
{
std::cout << obj << std::endl;
};
//
namespace pyfree{
class PVal
{
public:
int val;
};
std::ostream &operator<<(std::ostream &os, const PVal& obj)
{
os << "(";
os << obj.val;
os <<")";
return os;
};
};
//
template <typename T>
void func3(const T &obj)
{
std::cout << obj << std::endl;
};
#endif
test.cpp
#include "template_test.h"
#include <string>
int main(int argc, char* argv[])
{
std::cout << mymin<std::string>("guangzhou","shenzhen") << std::endl;
std::cout << mymax<std::string>("guangzhou","shenzhen") << std::endl;
//
DataTest adt(3,4),bdt(3,6);
mymin<DataTest>(adt,bdt);
//
A_Test<int,float> a(2,4.2),b(2,3.8);
std::cout << mymin<A_Test<int,float> >(a,b) << std::endl;
std::cout << mymax<A_Test<int,float> >(a,b) << std::endl;
//
A_Test<std::string,std::string> a_s("guangzhou","huangpu"),b_s("shenzhen","baoan");
if(a_s<b_s) std::cout << "a_s is min" << std::endl;
if(a_s>b_s) std::cout << "a_s is max" << std::endl;
//
std::cout << mymin<A_Test<std::string,std::string> >(a_s,b_s) << std::endl;
std::cout << mymax<A_Test<std::string,std::string> >(a_s,b_s) << std::endl;
//
A_Test<DataTest,DataTest> ad_s(DataTest(3,4),DataTest(4,1)),bd_s(DataTest(2,4),DataTest(3,1));
std::cout << mymin<A_Test<DataTest,DataTest> >(ad_s,bd_s) << std::endl;
std::cout << mymax<A_Test<DataTest,DataTest> >(ad_s,bd_s) << std::endl;
//
OUT_Class<int> o_a;
o_a.myfun1(10);
o_a.myfun2<double>(10.5);
//
OUT_Class<int>::INT_Class1<float> o_a_i1;
o_a_i1.val = 11.4;
OUT_Class<int>::INT_Class2<float> o_a_i2;
o_a_i2.val = 12.4;
//
union Chunk<int> uobj;
uobj.bytes[0] = 0xff;
std::cout << uobj.obj << std::endl;
//
int A_Test; //OK
//
CX<int> cx_i;
cx_i.val = 20;
func(cx_i);
CX<> cx_def;
cx_def.val = 21;
func(cx_def);
//
func2(cx_i);
func2(cx_def);
//
double val_ = im_cast<double>(-30);
std::cout << val_ << std::endl;
//
doSomething(doit<int>,3);
doSomething(&doit<int>,4);
//
void (*pfunc3)(const int &obj);
pfunc3 = func3<int>;
pfunc3(15);
//
typedef void (*PFunc)(const float &obj);
PFunc pf = func3<float>;
pf(12.5);
(*pf)(13.5);
//
pyfree::PVal pval;
pval.val = 33;
func3<pyfree::PVal>(pval);
return 0;
};