条款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 Msg{...};
template <typename Company>
class MsgSender{
public:
...
void sendClear(const MsgInfo& info){
std::string msg;
Company c;
c.sendCleartext(msg);
}
//sendSecret函数类似于sendClear函数
void sendSecret(const MsgInfo& info){
...
}
};
这种做法行得通,但假设我们此时想要在每次送出消息时候志记某些信息。derived class可轻易加上这样的生产力,这似乎是个合情合理的解法:
template <typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
...
void sendClearMsg(const MsgInfo& info){
sendClear(info);//调用base class函数,这段代码无法通过编译
};
...
};
这里derived class的信息传送函数有一个不同的名称,与其base class内的名称不同,但上述代码无法通过编译,无法发现sendClear函数,为什么???
原因在于,当编译器遇到LoggingMsgSender定义式时,并不知道它继承什么样的class,当然它继承的是MsgSender<Company>,但其中的Company是个template参数,不到LoggingMsgSender被具现化,无法确切知道它是什么,而如果不知道Company是什么,当然也就无法知道class MsgSender看起来像什么—更明确的说是没办法知道他是否有个senderClear函数。
为了让问题更加具体化,假设我们有个class CompanyZ坚持使用加密通讯:
class CompanyZ{
public:
...
void sendEncrypted(const std::string& msg);
...
};
一般行的MsgSender template对CompanyZ并不适合,因为template中多了一个sendClear函数,其中针对类型参数Company调用了sendCleartext函数,而这对CompanyZ对象并不合理。与纠正这个问题,我们针对CompanyZ产生一个MsgSender的特化版:
template<>
class MsgSender<CompanyZ>{
public:
...
void sendSecret(const MsgInfo{
...
}
};
注意class定义式前面的“template <>”语法表示这既不是template也不是class,而是一个特化版的MsgSender template,在template实参是CompanyZ时被使用,这是所谓的模板全特化:tempalte MsgSender针对类型CompanyZ特化了,而且其特化是全面性的,也就是说一旦类型参数被定义为CompanyZ,在没有其他template参数可供变化。
现在,MsgSender针对CompanyZ进行了全特化,让我们重新考虑derived class LoggingMsgSender:
template <typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
...
void sendClearMsg(const MsgInfo& info){
sendClear(info);//当Company==CompanyZ时候,这个函数并不存在
}
...
};
正如注释所示,当base class被指定为MsgSender<<>CompanyZ>时候这段代码不合适,因为那个class并未提供sendClear函数!那就是为什么C++拒绝这个调用的原因:他知道base class templates可能会被特化,而那个版本可能不提供和一般性template相同的接口,因此他往往拒绝在templatized base classes内寻找继承而来的名称,就某种意义而言,当我们从Object Oriented C++迈入Template C++时候,继承就变得不像以前那样畅通无阻了。
为了解决这个问题,我们必须有办法令C++“不进入templatized base classes观察”的行为失效。有三个办法:
1、 在base class动作之前加上“this->”:
template <typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
...
void sendClearMsg(const MsgInfo& info){
this->sendClear(info);
}
...
};
2、使用using声明式:
template <typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
using MsgSender<Company>::sendClear;
...
void sendClearMsg(const MsgInfo& info){
...
sendClear(info);
...
}
...
};
3、明确指出被调用的函数处于base class内:
template <typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
...
void sendClearMsg(const MsgInfo& info){
...
MsgSender<Company>::senderClear(info);
...
}
...
};
这种做法往往是最令人不满意的解法了,因为如果被调用的是virtual函数,上述的明确资格修饰会关闭“virtual绑定行为”。
PS:
在非模板类的函数中并不存在找不到函数的情况,详情见如下代码:
#include <iostream>
#include <string>
using namespace std;
class Base{
public:
Base(int xx) :x(xx){
}
virtual void show(){
cout << x << endl;
}
virtual void guessWhat()const = 0;
virtual ~Base(){
}
private:
int x;
};
class Derived :public Base{
public:
Derived(int x) :Base(x){
}
virtual void guessWhat()const{
cout << "I'm so grateful for meeting you!!!" << endl;
}
void showHello(){
show();
}
};
int main(){
Derived d(10);
d.showHello();
return 0;
}
运行结果:
PS:
针对全特化和偏特化,大家可以参考这篇博客哈,www.cnblogs.com/qlee/archive/2011/06/27/2091523.html,希望能够带给你更清晰的认识!!!
总结:
可在derived class templates中通过“this->”指涉base class templates内的成员名称,或者借由一个明白写出的“base class资格修饰符”完成。