条款43、学习处理模版化基类内的名称

考虑以下代码,功能为发送不同信息至不同部门,采用模版方法。
class CompanyA
{
         public:
         void sendClearT(const std::string&msg);
         void sendEncrypt(cons std::string& msg);
};
class CompanyB
{
         public:
         void sendClearT(const std::string&msg);
         void sendEncrypt(cons std::string& msg);

};
….. //其他公司的类

class MsgInfo{….};    //用来保存信息,以备用
template<typename Company>
class MsgSend
{
public:
….//构造 析构
         void sendClear(const MsgInfo& info)
{
         std::string  msg;
         …  //根据info产生信息
         Conpany c;      
         c.sendClearT(msg);      //合理
}
void sendSecret(const MsgInfo& info)  //格式同上
}
现在我们需要在发送信息前记录日志,我们可以很容易派生出来
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
….//构造 析构
         void sendClearMsg(const MsgInfo& info)
{
         //传送前记录log
         sendClear(info);        //调用基类函数,无法编译
         //传送后记录log
}
};

我们注意到这个派生类的信息传送函数有一个不同的名称(sendClearMsg),与其基类中的(sendClear)不同。这是个不错的设计,如TK33中的“避免掩盖继承而来的名称”,同时也避免了“重新定义一个继承而来的非虚函数”。但对于严格的编译器而言,上述代码无法通过编译(VC可以通过,G++不能)、、、、因为编译器看不到SendClear函数的存在。
问题在于:模版类logMsgSend定义时,并不知道它继承了什么样的类,虽然是MsgSend<Company>,但不到实例化时无法知道具体是什么、如果不知道Company是什么,也就无法知道MsgSend<Company>是什么。。。说到底就是,这种情况下无法知道它是否有SendClear函数。
 
我们实例来分析,假设有一个CompanyZ用于加密通讯
class CompanyZ
{
public:
         void sendEncrypted(const std::string & msg);
};
 
一般性的MsgSend模版对这种类并不合适,因为模板提供一个sendClear函数。如果对其类型参数调用SendClear函数,对CompanyZ对象并不合理。解决此问题的方法是提供针对CompanyZ提供一个MsgSend特化版本:
template<>
class MsgSend<CompanyZ>
{
public:   //一个全特化的MsgSend,和一般的模版相同,区别在于删掉了SendClear
         void sendEncrypted(const std::string & info)
         {…}
};
template<>”语法表示:这既不是标准类,也不是模版,而是个特化的MsgSend模版,在模版实参是CompanyZ时使用。这就是所谓的模板全特化:特化是全面的,即如果一旦参数特化为CompanyZ,就再没有其他模版参数可供变化。

现在全特化以后我们再次考虑派生类中的logSendClearMsg
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
….//构造 析构
         void sendClearMsg(const MsgInfo& info)
{
         //传送前记录log
         sendClear(info);        //如果Company=CompanyZ,这个函数不存在
         //传送后记录log
}
};

很显然当基类指定为MsgSend<CompanyZ>时这段代码不合法。这也是C+拒绝这种调用的原因:基类模版可能被物化。因此拒绝在模版化基类中寻找继承而来的名称。

解决办法1:
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
….//构造 析构
         voidsendClearMsg(const MsgInfo& info)
{
         //传送前记录log
         thi->sendClear(info);        //成立,假设senClear被继承
         //传送后记录log
}
};

方法2:使用using声明式
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
         usingMsgSend<Company>::sendClear;  //让编译器假设sendClear位于基类内
….//构造 析构
         void sendClearMsg(const MsgInfo& info)
{
         //传送前记录log
         thi->sendClear(info);        //成立,假设senClear被继承
         //传送后记录log
}
};

这里情况和避免派生类掩盖基类名称不同,而是编译器不进入基类作用域查找,于是我们告诉它,让它这么做。
方法3
template<typename Company>
class logMsgSend : public MsgSend<Company>
{
public:
….//构造 析构
         void sendClearMsg(const MsgInfo& info)
{
         //传送前记录log
         MsgSend<Company>::sendClear(info);        //成立,假设senClear将被继承
         //传送后记录log
}
};

但方法3并不是好的解法,因为如果被调用的是虚函数,那么明确修饰会关闭“virtual”的绑定行为。

上面三方法做的事情都相同:对编译器承诺“任何基类模版的特化版本”都支持其一般接口。
这个是编译器在解析派生类模版时需要的。如果出现下面源码:
logMsgSend<CompanyZ> zMsgSender;
MsgInf msgData;
zMsgSender.sendClear(msgData);  //错误,无法通过编译

在这个点上,编译器知道基个是个特化版本,且不提供sendClear函数。
 
根本而言:上面讨论的都是面对“基类成员的无效引用”,编译器的诊断可能发生于早期(解析派生类模版的定义式),也可能是晚期(模版被特定的模版参数实例化)。C++的原则是尽早诊断。这也说明了为什么“基类从模版中被实例化时”,它假设它对那些基类内容毫无所知。

需要记住的:
可在派生类模版中通过“this->”涉及基类模版内成员名,或明白写出“基类资格修饰符”
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值