访问模板化基类中名字



假设我们要写个应用它可以把消息传送到几个区别公司去消息既可以以加密方式也可以以明文(不加密)方式传送如果我们有足够信息在编译期间确定哪个消息将要发送给哪个公司我们就可以用个 template-based(模板基)来解决问题:
CompanyA {
  public:
  ...
  void sendCleartext(const std::& msg);
  void sendEncrypted(const std::& msg);
  ...
  };

   CompanyB {
  public:
  ...
  void sendCleartext(const std::& msg);
  void sendEncrypted(const std::& msg);
  ...
  };
  ... // es for other companies

   MsgInfo { ... }; // for holding information
  // used to create a message
  template
   MsgSender {
  public:
  ... // ctors, dtor, etc.

  void sendClear(const MsgInfo& info)
  {
  std:: msg;
  create msg from info;

  Company c;
  c.sendCleartext(msg);
  }
  void sendSecret(const MsgInfo& info) // similar to sendClear, except
  { ... } // calls c.sendEncrypted
  };
这个能够很好地工作但是假设我们有时需要在每次发送消息时候把些信息记录到日志中通过个 derived (派生类)可以很简单地增加这个功能下面这个似乎是个合理思路方法:
template
   LoggingMsgSender: public MsgSender {
  public:
  ... // ctors, dtor, etc.
  void sendClearmsg(const MsgInfo& info)
  {
  write "before sending" info to the log;
  sendClear(info); // call base function;
  // this code will not compile!
  write "after sending" info to the log;
  }
  ...
  };
注意 derived (派生类)中 message-sending function(消息发送)名字 (sendClearmsg) 和它 base (基类)中那个(在那里它被称为 sendClear)区别这是个好设计它避开了 hiding inherited names(隐藏继承来名字)问题(参见C箴言:避免覆盖通过继承得到名字)和重定义个 inherited non-virtual function(继承来非虚拟)和生俱来问题(参见C箴言:绝不重定义继承非虚拟)但是上面代码不能通过编译至少在符合标准编译器上不能这样编译器会抱怨 sendClear 不存在我们可以看见 sendClear 就在 base (基类)中但编译器不会到那里去寻找它我们有必要理解这是为什么

  问题在于当编译器遇到 template(类模板)LoggingMsgSender definition(定义)时它们不知道它从哪个 (类)继承当然它是 MsgSender但是 Company 是个 template parameter(模板参数)这个直到更迟些才能被确定(当 LoggingMsgSender 被例子化时候)不知道 Company 是什么就没有办法知道 (类)MsgSender 是什么样子特别是没有办法知道它是否有个 sendClear function()

  为了使问题具体化假设我们有个要求加密通讯 (类)CompanyZ:
CompanyZ { // this offers no
  public: // sendCleartext function
  ...
  void sendEncrypted(const std::& msg);
  ...
  };
MsgSender template(模板)不适用于 CompanyZ那个模板提供个 sendClear function()对于 CompanyZ objects(对象)没有意义为了纠正这个问题我们可以创建个 MsgSender 针对 CompanyZ 特化版本:
template<> // a total specialization of
   MsgSender { // MsgSender; the same as the
  public: // general template, except
  ... // sendCleartext is omitted
  void sendSecret(const MsgInfo& info)
  { ... }
  };
注意这个 definition(类定义)开始处 "template <>" 语法它表示这既不是个 template(模板)也不是个 standalone (独立类)正确说法是它是个用于 template argument(模板参数)为 CompanyZ 时 MsgSender template(模板) specialized version(特化版本)这以 total template specialization(完全模板特化)闻名:template(模板)MsgSender 针对类型 CompanyZ 被特化而且这个 specialization(特化)是 total(完全)——只要 type parameter(类型参数)被定义成了 CompanyZ就没有剩下能被改变其它 template's parameters(模板参数)
  已知 MsgSender 针对 CompanyZ 被特化再次考虑 derived (派生类)LoggingMsgSender: template
   LoggingMsgSender: public MsgSender {
  public:
  ...
  void sendClearmsg(const MsgInfo& info)
{
  write "before sending" info to the log;
  sendClear(info); // Company CompanyZ,
  // this function doesn't exist!
  write "after sending" info to the log;
  }
  ...
  };
就像注释中写当 base (基类)是 MsgSender 时这里代码是无意义那个类没有提供 sendClear function()这就是为什么 C 拒绝这个:它认可 base templates(基类模板)可以被特化而这个特化不定提供和 general template(通用模板)相同 erface(接口)结果它通常会拒绝在 templatized base es(模板化基类)中寻找 inherited names(继承来名字)在某种意义上当我们从 Object-oriented C 跨越到 Template Cinheritance(继承)会停止工作

  为了重新启动它我们必须以某种方式使 C "don't look in templatized base es"(不在模板基类中寻找)行为失效有 3种思路方法可以做到这首先你可以在 base functions(基类)前面加上 "this->":

template
   LoggingMsgSender: public MsgSender {
  public:
  ...

  void sendClearmsg(const MsgInfo& info)
  {
  write "before sending" info to the log;
  this->sendClear(info); // okay, assumes that
  // sendClear will be inherited
  write "after sending" info to the log;
  }
  ...
  };


第 2你可以使用个 using declaration如果你已经读过C箴言:避免覆盖通过继承得到名字这应该是你很熟悉种解决方案该文解释了 using declarations 如何将被隐藏 base names(基类名字)引入到个 derived (派生类)领域中因此我们可以这样写 sendClearmsg: template
   LoggingMsgSender: public MsgSender {
  public:
  using MsgSender::sendClear; // tell compilers to assume
  ... // that sendClear is in the
  // base
  void sendClearmsg(const MsgInfo& info)
  {
  ...
  sendClear(info); // okay, assumes that
  ... // sendClear will be inherited
  }
  ...
  };

{虽然 using declaration 在这里和C箴言:避免覆盖通过继承得到名字中都可以工作但要解决问题是区别
这里情形不是 base names(基类名字)被 derived names(派生类名字)隐藏而是如果我们不告诉它去做编译器就不会搜索 base 领域}

最后个让你代码通过编译办法是显式指定被是在 base (基类)中: template
   LoggingMsgSender: public MsgSender {
  public:
  ...
  void sendClearmsg(const MsgInfo& info)
  {
  ...
  MsgSender::sendClear(info); // okay, assumes that
  ... // sendClear will be
  } // inherited

  ...
  };
通常这是个解决这个问题最不合人心思路方法如果被是 virtual(虚拟)显式限定会关闭 virtual binding(虚拟绑定)行为

  从名字可见性观点来看这里每个思路方法都做了同样事情:它向编译器保证任何后继 base template(基类模板) specializations(特化)都将支持 general template(通用模板)提供 erface(接口)所有编译器在解析个像 LoggingMsgSender 这样 derived template(派生类模板)是这样种保证都是必要但是如果保证被证实不成立真相将在后继编译过程中暴露例如如果后面源代码中包含这些
LoggingMsgSender zMsgSender;
  MsgInfo msgData;
  ... // put info in msgData
  zMsgSender.sendClearmsg(msgData); // error! won't compile
对 sendClearMsg 将不能编译在此刻编译器知道 base (基类)是 template specialization(模板特化)MsgSender它们也知道那个 (类)没有提供 sendClearmsg 试图 sendClear function()

  从根本上说问题就是编译器是早些(当 derived template definitions(派生类模板定义)被解析时候)诊断对 base members(基类成员)非法引用还是晚些时候(当那些 templates(模板)被特定 template arguments(模板参数)例子化时候)再进行C 方针是宁愿早诊断而这就是为什么当那些 es(类)被从 templates(模板)例子化时候它假装不知道 base es(基类)内容

  Things to Remember

  ·在 derived templates(派生类模板)中可以经由 "this->" 前缀引用 base templates(基类模板)中名字经由 using declarations或经由个 eXPlicit base qualication(显式基类限定)
Tags: 

延伸阅读

最新评论

发表评论