云枫的菜园子

带着一个流浪的心,慢慢沉淀。 https://github.com/CloudFeng

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

     开篇就来了一个示例代码,整个这个小节就围绕这个示例代码来描述模板化基类内的名称(函数)。主要是因为C++知道base class templates有可能被特化,而那个特化版本肯呢个不提供和一般性template相同的接口。因此它往往拒绝在templatized base classes(模板化基类)内寻找继承而来的名称。

 1 class CompanyA {
 2  public:
 3      //...
 4      void sendCleartext(const std::string& msg);
 5      void sendEncrypted(const std::string& msg);
 6      //...
 7  };
 8 
 9  class CompanyB {
10  public:
11      //...
12      void sendCleartext(const std::string& msg);
13      void sendEncrypted(const std::string& msg);
14      //...
15  };
16 
17  //...  针对其他公司设计的类
18  
19  /*用来保存信息,以备将来产生信息*/
20  class MsgInfo {
21      //...
22  };
23 
24 template<typename Company>
25 class MsgSender {
26 public:
27     //... 构造函数、析构函数等
28     void sendClear(const MsgInfo& info) 
29     {
30         std::string msg;
31         // 根据info产生信息
32         Company c;
33         c.sendCleartext(msg);
34     }
35 
36     void sendSecret(const MsgInfo& info)
37     {
38         std::string msg;
39         // 根据info产生信息
40         Company c;
41         c.sendEncryptedtext(msg);
42     }
43 };
44 
45 /* 日志:记录每个信息发送相关记录 */
46 template<typename Company>
47 class LoggingMsgSender:public MsgSender<Company> {
48     //... 构造函数、析构函数等
49     void sendClearMsg(const MsgInfo& info) 
50     {
51 
52         // 将“传送前”的信息写到log
53         sendClear(info);             // 调用base class函数 (error:不同通过编译)
54         // 将“传送后”的信息写到log
55     }
56 
57 };
58 
59 /*调用base class函数 (error:不同通过编译)的原因:
60   模板类型在编译前不知道Company是否有sendClear函数,比如下面这个 CompanyZ class
61  */
62 class CompanyZ {
63     // 不提供sendClearText函数
64     //...
65      void sendEncrypted(const std::string& msg);
66      //...
67 };

为了解决上述问题:可以使用为 CompanyZ,搞个绿色通道:
  1.class 定义式前面加 template<> 表明:它既不是template也不是标准的class,而是一个特化版的MsgSender template,在template实参是CompanyZ时被使用。
  2.模板全特化(total template specialization):template MsgSender针对类型CompanyZ特化了,而且其特化是全面性的,也就说一旦类型参数被指定为CompanyZ,再没有其他template参数可供变化。

1 template<>            // 一个全特化的MsgSender,与一般template相同,差别只在于它删掉了sendClear
2 class MsgSender<CompanyZ> {
3     //...
4     void sendSecret(const MsgInfo& info)
5     {
6         sendEncryptedtext(info);
7     }
8 };
 1 /* 再来看,日志:记录每个信息发送相关记录 */
 2 template<typename Company>
 3 class LoggingMsgSender:public MsgSender<Company> {
 4     //... 构造函数、析构函数等
 5     void sendClearMsg(const MsgInfo& info) 
 6     {
 7 
 8         // 将“传送前”的信息写到log
 9         sendClear(info);             
10         /*
11             如果Company == CompanyZ, CompanyZ不存在sendClear函数,报错。
12             原因:编译器知道base class templates有可能被特化,而那个特化版本肯呢个不提供
13             和一般性template相同的接口。因此它往往拒绝在templatized base classes(模板化基类)内寻找继承而来的名称。
14         */
15         // 将“传送后”的信息写到log
16     }
17 
18 };

 

让C++“不进入templatized base classes观察”的行为失效的解决方法如下:

  1. 在 base class 函数调用动作之前加上 “this->”:

 1 /* 日志:记录每个信息发送相关记录 */
 2 template<typename Company>
 3 class LoggingMsgSender:public MsgSender<Company> {
 4     //... 构造函数、析构函数等
 5     void sendClearMsg(const MsgInfo& info) 
 6     {
 7 
 8         // 将“传送前”的信息写到log
 9         this->sendClear(info);             
10         // 将“传送后”的信息写到log
11     }
12     //...
13 };

 

2.使用using声明式
  这里并不是base class名称被derived class名称遮掩,而是编译器不进入base class作用域内查找,于是通过using告诉它,请它那么做。

 1 /* 日志:记录每个信息发送相关记录 */
 2 template<typename Company>
 3 class LoggingMsgSender:public MsgSender<Company> {
 4     using MsgSender<Company>::sendClear;            // 告诉编译器,清它假设sendClear位于base class内
 5     //... 构造函数、析构函数等
 6     void sendClearMsg(const MsgInfo& info) 
 7     {
 8 
 9         // 将“传送前”的信息写到log
10         sendClear(info);             
11         // 将“传送后”的信息写到log
12     }
13     //...
14 };

 

3.明确指出被调用的函数位于base class内
  缺点:如果被调用的是virtual函数,明确自个修饰(explicit qualification) 会关闭"virtual绑定行为"

 1 /* 日志:记录每个信息发送相关记录 */
 2 template<typename Company>
 3 class LoggingMsgSender:public MsgSender<Company> {
 4     //... 构造函数、析构函数等
 5     void sendClearMsg(const MsgInfo& info) 
 6     {
 7 
 8         // 将“传送前”的信息写到log
 9         MsgSender<Company>::sendClear(info);             
10         // 将“传送后”的信息写到log
11     }
12     //...
13 };

 

总结: 

  1. 三种方法都是从名称可视点(visibility point)的角度出发:

  对编译器承诺“base class template”的任何特化版本都将支持其一般(泛化)版本所提供的接口。[编译器的早期诊断时间:当解析derived class template的定义式时];如果这个承诺没有被实践,往后的编译器最终会给事实一个公道。[在编译器晚期诊断时间:当那些templates被特定的template具体化时]。如下面的示例:

1  LoggingMsgSender<CompanyZ> zMsgSender;
2  MsgInfo msgData;
3  //...
4  zMsgSender.sendClearMsg(msgData);   // error

  2.可在derived class templates内通过"this->"指涉base class templates内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成。


 

声明:全文文字都是来源《Effective C++》 3th。这里所写都是自己的读书的时候梳理做的笔记罢了。希望对他人有用,那是我的荣幸。

posted on 2015-04-12 11:27  CloudFeng  阅读(690)  评论(0编辑  收藏  举报