[toc]
模板化的类作为基类时,有哪些要注意的地方。以一个例子说明,假设现在编写一个发送信息到不同公司的程序,信息要么译成密码,要么就是原始文字,在编译期间来决定哪一家公司发送至哪一家公司,采用template手法:
class CompanyA{
public:
void sendCleartext(const std::string& msg);
void sendEncryted(const std::string& msg);
……
};
class CompanyB{
public:
void sendCleartext(const std::string& msg);
void sendEncryted(const std::string& msg);
……
};
……//还有一些公司
class MsgInfo{……};//用来保存信息,以备将来产生信息
template<typename Company>
class MsgSender{
public:
……//构造析构等函数
void sendClear(const MsgInfo& info)
{
std::string msg;
//根据info产生信息
Company c;
c.sendCleartext(msg);
}
void sendSecart(const MsgInfo& info)
{……}
};
上面这个做法行得通,但是如果要在每次送出信息时记录日志,可以派生出derived class,加上记录的日志
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>
{
public:
……//析构构造等
void SendClearMsg(const MsgInfo& info)
{
//发送前的信息写到log
sendClear(info);//调用base class函数,这段代码无法通过编译
//传送后信息写到log
}
};
在派生类中,sendClearMsg和base class中的sendClear不同,这是个好设计,避免遮掩继承而得的名称,也避免了重新定义一个继承而得non-virtual函数。但是编译不能通过,因为编译器看不到sendClear函数。
因为当编译器遇到class template LoggingMsgSender定义式时,不知道它继承什么样的class,因为MsgSender中的Company是个参数,在LoggingMsgSender被具体化之前,无法确切知道它是什么,自然而然就不知道class MsgSender是什么,也就不知道它是否有个sendClear函数。(备注,在g++中可以使用参数 -fpermissive参数来通过编译,但是不建议使用)
为了让问题更具体化,假设现在有个class CompanyZ坚持使用加密通讯
class CompanyZ{
pubic:
void sendEncryted(const std::sting& msg);
……
};
CompanyZ没有sendClear函数,一般性的MsgSender template对CompanyZ并不合适,这时我们可以针对CompanyZ产生一个MsgSender特化版
template<>
class MsgSender<CompanyZ>{
public:
void sendSecret(const MsgInfo& infof)
{……}
……
};
开头的template<>
表示是特化版的MsgSender template,在template实参是CompanyZ时被使用。这就是所谓的模板全特化(total template specialization):template MsgSender针对类型CompanyZ特化了,且是全面性特化,即一旦类型参数为CompanyZ,没有其他template参数可供变化了。
再来看一下刚刚的LoggingMsgSender
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>
{
public:
……//析构构造等
void SendClearMsg(const MsgInfo& info)
{
//发送前的信息写到log
sendClear(info);//如实Company是CompanyZ,那么这个函数不存在
//传送后信息写到log
}
};
如果Company=CompanyZ,那么sendClear函数就不存在。C++拒绝调用这个函数是因为它知道base class template可能被特化,而那个特化版本可能不提供和一般性template相同的接口。所以它往往拒绝在templatized base classes(模板化基类)中寻找继承而来的名称。当我们从Object Oriented C++跨进到Template C++时,继承不像以前那样畅行无阻了。
现在应该讨论一下怎么解决上面不能通过编译的问题了。我们必须有某种办法令C++“不进入templatized base class观察”的行为失效。有一下三种办法
1、在base class函数调用动作之前加上“this->”
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>
{
public:
……//析构构造等
void SendClearMsg(const MsgInfo& info)
{
//发送前的信息写到log
this->sendClear(info);
//传送后信息写到log
}
};
2、使用using声明式,有点类似**条款**33。
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>
{
public:
……//析构构造等
uinsg MsgSender<Company>::sendClear;//告诉编译器,假设sendClear位于base class内
void SendClearMsg(const MsgInfo& info)
{
//发送前的信息写到log
sendClear(info);//可以编译通过,假设sendClear将被继承
//传送后信息写到log
}
};
补充一下,这里情况和**条款**33不同,这里不是将被掩盖的base class名称带入一个derived class作用域内,而是编译器不进入base class作用域内查找,通过using告诉编译器,请它去查找。
3、明白指出被调用的函数位于base class内
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>
{
public:
……//析构构造等
void SendClearMsg(const MsgInfo& info)
{
//发送前的信息写到log
MsgSender<Company>::sendClear(info);//可以编译通过,假设sendClear被继承
//传送后信息写到log
}
};
这种做法使用了明确资格修饰符(explicit qualification),这将会关闭virtual绑定行为。
回头再看上面三种做法,原理相同:对编译器承诺“base class template的任何特化版本都将支持其一般(泛化)版本所提供的接口”。这样的承诺是编译器在解析(parse)像LoggingMsgSender这样的derived class template时需要的。但如果这个承诺稍后没有兑现,编译器还会给事实一个公道。例如,稍后源码内:
LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
zMsgSender.sendClearMsg(msgData);//错误,无法编译通过
在调用sendClearMsg这个点上,编译器直到base class是个template特化版本,且它直到这个特化版不提供sendClearMsg函数。
总结一下,本条款讨论的是,面对指涉base class member之无效references,编译器的诊断时间可能发生在早起(当解析derived class template定义时),也可能发生在晚期(当template被特定值template实参具体化时)。C++宁愿较早诊断,这也是为什么当base classes从template具体化时,它假设对那些base classes的内容毫无所悉的缘故。
总结
《Effective C++》:条款43:学习处理模板化基类内的名称
原文地址:http://blog.csdn.net/kangroger/article/details/44205331