条款43:学习处理模板化基类内的名称(Know how to access names in templatized base classes)
条款43:学习处理模板化基类内的名称
假设需要编写一个可以向多个不同的公司发送消息的应用程序。
class CompanyA {
public:
...
void sendCleartext(const std::string& msg); // 明文发送
void sendEncrypted(const std::string& msg); // 加密发送
...
};
class CompanyB {
public:
...
void sendCleartext(const std::string& msg); // 明文发送
void sendEncrypted(const std::string& msg); // 加密发送
...
};
... // 其他公司的类
class MsgInfo { ... }; // 用于保存信息的类,将来可以用生成消息
template<typename Company>
class MsgSender {
public:
... // ctors, dtor, 等等.
void sendClear(const MsgInfo& info)
{
std::string msg;
...// 通过info创建消息;
Company c;
c.sendCleartext(msg);
}
void sendSecret(const MsgInfo& info) //
{
... // 与sendClear类似,但会调用c.sendEncrypted
}
};
假设我们有时想在每次发送消息时记录一些信息:
template<typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
... // ctors, dtor, 等.
void sendClearMsg(const MsgInfo& info){
...//在日志中写入"发送前"信息;
//编译器会报错:sendClear不存在
sendClear(info); // 调用基类函数;无法编译通过!!
...//在日志中写入"发送后"信息;
}
...
};
为了让问题具体化,假设我们有一个类CompanyZ,它坚持使用加密通信:
class CompanyZ { // 这个类没有提供sendCleartext函数
public:
...
void sendEncrypted(const std::string& msg);
...
};
template<> // 全特化
//基类模板可以进行特化,而且这种特化可能不会提供与通用模板相同的接口
class MsgSender<CompanyZ> { // 没有sendCleartext,其他都和泛化的版本一样
public:
...
void sendSecret(const MsgInfo& info)
{
...
}
};
回想一下前面的类模板:
//当基类是MsgSender<CompanyZ>时,此代码没有意义
template<typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info){...;sendClear(info); ...;
...
};
当我们从面向对象C++过渡到模板C++时,继承体系似乎失效了。需要重启它:
- 可以使用“this->”调用基类函数:
template<typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
...//在日志中写入"发送前"信息;
this->sendClear(info); // OK!假设sendClear将被继承
...//在日志中写入"发送后"信息;
}
...
};
- 可以使用using声明
template<typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear; // 告诉编译器,请它假设基类里有sendClear
...
void sendClearMsg(const MsgInfo& info)
{
..
sendClear(info); // OK!!假设sendClear将被继承
...
}
...
};
- 要明确指定被调用的函数在基类中有定义:
template<typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
...
MsgSender<Company>::sendClear(info); // OK!!假设sendClear将被继承
...
}
...
//这通常是解决该问题最不理想的方法,因为如果被调用的函数是虚函数,显式限定将关闭虚绑定行为。
};
这些方法都做了同样的事情:向编译器承诺,基类模板的任何后续特化都将支持通用模板提供的这些接口。如果违反了承诺,编译将会失败:
LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
... // 将info放入msgData
zMsgSender.sendClearMsg(msgData); // 错误! 编译失败
注:C++的政策倾向于早期诊断,所以当从模板实例化这些类时,它假设自己基类一无所知。(也就是强迫程序员提供更多的信息(或承诺))。
总结:可在派生类的模板内通过“this- >”、using声明、或通过显式基类限定完成成员名称的指定。