考虑以下代码,功能为发送不同信息至不同部门,采用模版方法。
class CompanyA
{
public:
void sendClearT(const std::string&msg);
void sendEncrypt(cons std::string& msg);
};
class CompanyB
{
public:
void sendClearT(const std::string&msg);
void sendEncrypt(cons std::string& msg);
};
….. //其他公司的类
class MsgInfo{….}; //用来保存信息,以备用
template<typename Company>
class MsgSend
{
public:
….//构造 析构
void sendClear(const MsgInfo& info)
{
std::string msg;
… //根据info产生信息
Conpany c;
c.sendClearT(msg); //合理
}
void sendSecret(const MsgInfo& info) //格式同上
}
现在我们需要在发送信息前记录日志,我们可以很容易派生出来
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
….//构造 析构
void sendClearMsg(const MsgInfo& info)
{
//传送前记录log
sendClear(info); //调用基类函数,无法编译
//传送后记录log
}
};
我们注意到这个派生类的信息传送函数有一个不同的名称(sendClearMsg),与其基类中的(sendClear)不同。这是个不错的设计,如TK33中的“避免掩盖继承而来的名称”,同时也避免了“重新定义一个继承而来的非虚函数”。但对于严格的编译器而言,上述代码无法通过编译(VC可以通过,G++不能)、、、、因为编译器看不到SendClear函数的存在。
问题在于:模版类logMsgSend定义时,并不知道它继承了什么样的类,虽然是MsgSend<Company>,但不到实例化时无法知道具体是什么、如果不知道Company是什么,也就无法知道MsgSend<Company>是什么。。。说到底就是,这种情况下无法知道它是否有SendClear函数。
我们实例来分析,假设有一个CompanyZ用于加密通讯
class CompanyZ
{
public:
void sendEncrypted(const std::string & msg);
};
一般性的MsgSend模版对这种类并不合适,因为模板提供一个sendClear函数。如果对其类型参数调用SendClear函数,对CompanyZ对象并不合理。解决此问题的方法是提供针对CompanyZ提供一个MsgSend特化版本:
template<>
class MsgSend<CompanyZ>
{
public: //一个全特化的MsgSend,和一般的模版相同,区别在于删掉了SendClear
void sendEncrypted(const std::string & info)
{…}
};
“template<>”语法表示:这既不是标准类,也不是模版,而是个特化的MsgSend模版,在模版实参是CompanyZ时使用。这就是所谓的模板全特化:特化是全面的,即如果一旦参数特化为CompanyZ,就再没有其他模版参数可供变化。
现在全特化以后我们再次考虑派生类中的logSendClearMsg
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
….//构造 析构
void sendClearMsg(const MsgInfo& info)
{
//传送前记录log
sendClear(info); //如果Company=CompanyZ,这个函数不存在
//传送后记录log
}
};
很显然当基类指定为MsgSend<CompanyZ>时这段代码不合法。这也是C+拒绝这种调用的原因:基类模版可能被物化。因此拒绝在模版化基类中寻找继承而来的名称。
解决办法1:
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
….//构造 析构
voidsendClearMsg(const MsgInfo& info)
{
//传送前记录log
thi->sendClear(info); //成立,假设senClear被继承
//传送后记录log
}
};
方法2:使用using声明式
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
usingMsgSend<Company>::sendClear; //让编译器假设sendClear位于基类内
….//构造 析构
void sendClearMsg(const MsgInfo& info)
{
//传送前记录log
thi->sendClear(info); //成立,假设senClear被继承
//传送后记录log
}
};
这里情况和避免派生类掩盖基类名称不同,而是编译器不进入基类作用域查找,于是我们告诉它,让它这么做。
方法3
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
….//构造 析构
void sendClearMsg(const MsgInfo& info)
{
//传送前记录log
MsgSend<Company>::sendClear(info); //成立,假设senClear将被继承
//传送后记录log
}
};
但方法3并不是好的解法,因为如果被调用的是虚函数,那么明确修饰会关闭“virtual”的绑定行为。
上面三方法做的事情都相同:对编译器承诺“任何基类模版的特化版本”都支持其一般接口。
这个是编译器在解析派生类模版时需要的。如果出现下面源码:
logMsgSend<CompanyZ> zMsgSender;
MsgInf msgData;
zMsgSender.sendClear(msgData); //错误,无法通过编译
在这个点上,编译器知道基个是个特化版本,且不提供sendClear函数。
根本而言:上面讨论的都是面对“基类成员的无效引用”,编译器的诊断可能发生于早期(解析派生类模版的定义式),也可能是晚期(模版被特定的模版参数实例化)。C++的原则是尽早诊断。这也说明了为什么“基类从模版中被实例化时”,它假设它对那些基类内容毫无所知。
需要记住的:
可在派生类模版中通过“this->”涉及基类模版内成员名,或明白写出“基类资格修饰符”