有个程序要传送信息到不同公司,信息可能是密码也可以能使未经处理的。传送的不同公司的代码不同,在编译期间我们有足够信息决定传送到哪一个公司,于是采用基于template的解法:
class CompanyA{
public:
void sendClrText(const std::string& str)
{
cout<<"compA::sendClrText==>"<<str.data()<<endl;
}
void sendEncrypted(const std::string& str)
{
cout<<"compA::sendEncrypted==>"<<str.data()<<endl;
}
};
class CompanyB{
public:
void sendClrText(const std::string& str)
{
cout<<"compB::sendClrText==>"<<str.data()<<endl;
}
void sendEncrypted(const std::string& str)
{
cout<<"compB::sendEncrypted==>"<<str.data()<<endl;
}
};
class MsgInfo{
public:
MsgInfo(const std::string& msg)
:rmsg(msg)
{}
enum MSGLEVEL{ //declaration must add ";" at end, defination cannt add ";"
NON = 0,
ENC,
};
std::string createMsg(MSGLEVEL lev) const
{
std::string tmp;
switch(lev)
{
case NON:
tmp = rmsg;
break;
case ENC:
tmp = "******"+rmsg+"******";
break;
default:
break;
}
return tmp;
}
std::string rmsg;
};
template<typename Company>
class MsgSender
{
public:
void sendClear(const MsgInfo& info){
std::string msg;
msg = info.createMsg(MsgInfo::NON);
Company c;
c.sendClrText(msg);
}
void sendSecret(const MsgInfo& info){
std::string msg;
msg = info.createMsg(MsgInfo::ENC);
Company c;
c.sendEncrypted(msg);
}
};
这种解法可行,验证:
MsgSender<CompanyA> senda;
MsgInfo ma("PPPP");
senda.sendClear(ma);
senda.sendSecret(ma);
MsgSender<CompanyB> sendb;
MsgInfo mb("QQQQ");
sendb.sendClear(mb);
sendb.sendSecret(mb)
结果:
现在假设要在发出信息的前后做一些log,考虑用继承类来实现:
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
{
public:
void sendClearMsg(const MsgInfo& info){
cout<<"logging start"<<endl;
//sendClear(info); //err,compiler doesnt know MsgSender<company> whether has thisfunc or not
cout<<"logging end"<<endl;
}
void sendSecretMsg(const MsgInfo& info){
cout<<"logging start"<<endl;
//sendSecret(info);
cout<<"logging end"<<endl;
}
};
这里的sendClearMsg避免名称遮掩,也避免重新定义继承而得的非虚函数(好设计),但是编译器因看不到sendClear而报错。问题在于,基类是个模板,编译器无法确定具现化后到底有没有此函数。比如有个仅仅使用加密通讯的CompanyZ:
class CompanyZ{
public:
void sendEncrypted(const std::string& str)
{
cout<<"compZ::sendEncrypted==>"<<str.data()<<endl;
}
};
由于一般的MsgSender模板提供了发送原始信息的接口,其不适用于CompanyZ。因此针对CompanyZ将MsgSender进行全特化(此MsgSender参数完全确定,已经不是模板):
template<> //total template specialization
class MsgSender<CompanyZ>
{
public:
void sendSecret(const MsgInfo& info){
std::string msg;
msg = info.createMsg(MsgInfo::ENC);
CompanyZ c;
c.sendEncrypted(msg);
}
};
再次考虑继承类,因为当基类被注定为MsgSender<CompanyZ>时,没有sendClear函数,自然就不合法了!因为 编译器知道基类模板可能被特化,而那个特化版本却和一般版本接口不一致,它拒绝在模板化基类中寻找继承而来的名称。同样,即便CompanyZ有sendSecret函数,编译器也无法通过。
有三个方法使得编译器在模板化基类中寻找继承来名称:
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>
{
public:
//using MsgSender<Company>::sendClear; //2nd. tell compiler to find in base class scope
void sendClearMsg(const MsgInfo& info){
cout<<"logging start"<<endl;
this->sendClear(info); //1st, assume sendclear will be inheritted
//sendClear(info); //2nd
//MsgSender<Company>::sendClear(info);//3rd,not good,if func is virtual,is will close virtual function
cout<<"logging end"<<endl;
}
void sendSecretMsg(const MsgInfo& info){
cout<<"logging start"<<endl;
this->sendSecret(info);
cout<<"logging end"<<endl;
}
};
其一是 在函数调用时加上“this->”,假设sendClear将被继承;
其二是使用using声明式,告诉编译器,请它去基类中寻找sendClear;
其三是直接指出调用基类中的sendClear。(此法最不好,如果sendClear是虚函数,它将会关闭虚函数的绑定行为)
不论哪一种方法,都是对编译器承诺任何模板化基类的任何特化版本都支持一般(泛化)版本所提供接口。但这个承诺没有被实践出来,往后编译依旧报错:
LoggingMsgSender<CompanyZ> logsendz;//2nd will err, using...
MsgInfo lmz("lmz");
logsendz.sendClearMsg(lmz); //err whichever method is used
这么说,面对基类模板中可能性出现的无效接口,以上所讨论的只不过是将编译错误延后而已。C++准则是宁愿较早诊断,这也是为何编译器不去模板化基类中寻找名称的缘故。
PS:用using声明式的方法时,实例化 CompanyZ类型LoggingMsgSender的时候也会报错(当然CompanyA、CompanyB类型没问题):
可见using方法,会使编译器在模板实例化时,优先查找其基类的CompanyZ特化版本内IF是否有效。否则拒绝实例化而不是类似于方法一、三直到函数调用才报错(它们是假定IF肯定被实践出来了)。哈哈,这也算将稍将编译错误提前侦测出来了。