开篇就来了一个示例代码,整个这个小节就围绕这个示例代码来描述模板化基类内的名称(函数)。主要是因为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。这里所写都是自己的读书的时候梳理做的笔记罢了。希望对他人有用,那是我的荣幸。
所有内容都是用BSD条款。 Copyright (C) by CloudFeng.