一、模板中,派生类不可调用模板基类的成员函数
- 在模板类中,如果一个派生类在其方法中调用了基类的方法,那么这段代码可能无法编译通过
- 备注(重点):
- 这一现象是与编译器有关的,Effective C++的作者编译的时候出错
- 本人使用VS编译的时候没有出错
- 因此这个问题是与编译器有关
演示说明
- 假设现在有这样一个类体系:
- 我们有若干公司类,其包含两个成员函数可以用来将信息发送到公司(一个为发送加密信息,一个为发送不加密信息)
- 有一个MsgSender类,其中有两个成员函数,函数中可以定义若干公司,然后调用公司的成员方法向公司发送信息
- 有一个MsgInfo类,用来封装消息(这个类不重要)
- 代码如下:
//公司类 class CompanyA { public: void sendCleartext(const std::string& msg); //向公司发送未加密信息 void sendEncrypted(const std::string& msg); //向公司发送加密信息 }; class MsgInfo {}; //封装信息的类 //发送信息类 template<typename Company> class MsgSender { public: //在其中定义公司A,并调用公司A的sendCleartext()函数向公司A发送信息 void sendClear(const MsgInfo& info) { std::string msg; CompanyA a; a.sendCleartext(msg); } //同上,只是发送加密信息 void sendSecret(const MsgInfo& info) { std::string msg; CompanyA a; a.sendEncrypted(msg); } };
- 现在我们为MsgSender类添加了一个派生类,我们希望每次在发送信息的时候记录一下日志。因此定义如下:
template<typename Company> class LoggingMsgSender:public MsgSender<Company>{ public: void sendClearMsg(const MsgInfo& info) { //记录一下日志 sendClear(info); //调用基类的方法发送消息,这段代码可能无法编译通过 //记录一下日志 } void sendSecretMsg(const MsgInfo& info) { //记录一下日志 sendSecret(msg); //调用基类的方法发送消息,这段代码可能无法编译通过 //记录一下日志 } };
- 上面代码对于某些编译器会出错的原因在于:
- 编译期出错:当类遇到LoggingMsgSender类模板定义式时,其并不知道LoggingMsgSender继承的class属于什么类型,因为还没有具体被实例化
- 因此在编译到LoggingMsgSender的成员函数时,其并不知道其基类是否有一个sendClear()函数
针对于错误的原因,我们再做一个演示案例
- 上面介绍了,在编译派生类调用基类成员函数的时候编译器出错。现在我们再看一个演示案例,能够更加让你理解这种错误的原因
- 例如:
- 现在我们有一个公司Z,其接收的消息只支持加密方式,因此,公司Z中只定义sendEncrypted()函数
- 因为公司Z只发送加密消息,所以对于前面定义的MsgSender类模板就不适合于我们的公司Z了,因为公司Z不需要发送普通的消息。因此我们需要针对于公司Z全特化一个MsgSender类模板
- 代码如下:
//公司Z,只接收加密消息 class CompanyZ { public: void sendEncrypted(const std::string& msg); }; //针对于公司Z的全特化版本,发送加密消息 template<> class MsgSender<CompanyZ> { public: void sendSecret(const MsgInfo& info) { std::string msg; CompanyZ z; z.sendEncrypted(msg); } };
- 上面针对于MsgSender进行了全特化,现在我们再来看看上面的LoggingMsgSender类模板为什么会报错:
- 如果LoggingMsgSender的基类MsgSender的类型为CompanyZ时,那么下面的代码就是错误的了
- 因为CompanyZ不拥有sendClear函数
template<typename Company> class LoggingMsgSender:public MsgSender<Company>{ public: //如果Company的类型为CompanyZ,,那么此处会出错,因为CompanyZ不拥有sendClear函数 void sendClearMsg(const MsgInfo& info) { sendClear(info); } void sendSecretMsg(const MsgInfo& info) { sendSecret(info); } };
- 错误的原因总结:
- 我们在编写非模板类的时候,在派生类中调用某函数时,如果在本类中没有查找到该函数,那么就会向基类的作用域中去查找函数
- 但是模板类不一样,在派生类中调用某函数时,如果在本类中没有查找到该函数,那么其不会继续向基类中进行查找
二、解决上面错误的3种方法
第一种方法
- 使用this指针:使用this指针调用这些函数,实现先告诉编译器这些函数是属于自身类的(在编译之后它们会从基类中继承而来)
- 例如:
template<typename Company> class LoggingMsgSender:public MsgSender<Company>{ public: void sendClearMsg(const MsgInfo& info) { //记录一下日志 this->sendClear(info); //记录一下日志 } void sendSecretMsg(const MsgInfo& info) { //记录一下日志 this->sendSecret(info); //记录一下日志 } };
第二种方法
使用using声明式
注意这种using声明式与非模板类的不同:
在非模板类中,使用using是为了防止派生类隐藏继承的方法,而使基类中的方法在派生类中可见
在模板类中,使用using是为了让编译器去基类中查找这个函数
例如:
template<typename Company> class LoggingMsgSender:public MsgSender<Company>{ public: using MsgSender<Company>::sendClearMsg; using MsgSender<Company>::sendSecretMsg; void sendClearMsg(const MsgInfo& info) { //记录一下日志 sendClear(info); //记录一下日志 } void sendSecretMsg(const MsgInfo& info) { //记录一下日志 sendSecret(info); //记录一下日志 } };
第三种方法
- 明确指出被调用的函数位于base class中
- 这种方法不太建议,因为:被调用的函数可能是virtual函数,这种修饰符会关闭“virtual绑定行为”
- 例如:
template<typename Company> class LoggingMsgSender:public MsgSender<Company>{ public: void sendClearMsg(const MsgInfo& info) { //记录一下日志 sgSender<Company>::sendClear(info); //记录一下日志 } void sendSecretMsg(const MsgInfo& info) { //记录一下日志 sgSender<Company>::sendSecret(info); //记录一下日志 } };
- 总结:
- 上面介绍了三种方法都是可以解决让模板类编译通过的方法
- 对于全特化版本来说也可以编译通过,直到调用不存在的方法时才会报错。例如对上面的CompanyZ的全特化MsgSender版本调用sendClearMsg函数时就会报错。例如:
class CompanyZ { };
class MsgInfo {};
template<typename Company>
class MsgSender { };
//全特化版本
template<>
class MsgSender<CompanyZ> {
public:
void sendSecret(const MsgInfo& info)
{
std::string msg;
CompanyZ z;
z.sendEncrypted(msg);
}
};
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
void sendClearMsg(const MsgInfo& info)
{
this->sendClear(info);
}
void sendSecretMsg(const MsgInfo& info)
{
this->sendSecret(info);
}
};
int main()
{
//此段代码仍然可以编译通过,即使CompanyZ不支持sendClearMsg
LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
zMsgSender.sendSecretMsg(msgData);
//zMsgSender.sendClearMsg(msgData); //不能调用这一句,否则报错
return 0;
}
三、总结
- 可在derived class templates内通过“this”指涉base class templates内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成