读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字
1. 问题的引入——派生类不会发现模板基类中的名字
假设我们需要写一个应用,使用它可以为不同的公司发送消息。消息可以以加密或者明文(未加密)的方式被发送。如果在编译阶段我们有足够的信息来确定哪个信息会被发送到哪个公司,我们可以使用基于模板的解决方案:
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);
...
}; ... // classes for other companies class MsgInfo { ... }; // class for holding information
// used to create a message template<typename Company>
class MsgSender {
public:
... // ctors, dtor, etc. void sendClear(const MsgInfo& info)
{
std::string msg;
create msg from info;
Company c;
c.sendCleartext(msg);
}
void sendSecret(const MsgInfo& info) // similar to sendClear, except { ... } // calls c.sendEncrypted }
这会工作的很好,但是假设有时候我们需要在发送信息之前log一些信息。一个派生类就能够很容易的添加这些信息,下面的实现看上去是合理的实现方式:
template<typename Company> class LoggingMsgSender: public MsgSender<Company> { public: ... // ctors, dtor, etc. void sendClearMsg(const MsgInfo& info) { write "before sending" info to the log; sendClear(info); // call base class function;
// this code will not compile!
write "after sending" info to the log;
}
...
};
注意派生类中的消息发送函数和基类相比(sendClear)是一个不同的名字(sendClearMsg)。这是好的设计,因为这避免了隐藏继承而来的名字的问题(Item 33),同时避免了重新定义继承而来的非虚函数问题(Item 36)。但是代码不能通过编译,至少符合标准的编译器不能通过编译。这些编译器会发出sendClear不存在的抱怨。我们能够看到sendClear是在基类中,但是编译器没有在基类中发现它。我们需要知道为什么。
2. 问题分析
2.1 一般化分析
问题出现在当编译器遇到类模版LoggingMsgSender的定义时,它们不知道它继承自什么类。当然,它是继承自MsgSender<Company>,但是Company是一个模板参数,这个参数只有在LoggingMsgSender被实例化的时候才会被确认。在不知道Company是什么的情况下,我们也不知道MsgSender<Company>是什么样子的。因此也就没有方法获知是否存在sendClear函数。
2.2 用实例来证明问题所在
为了使问题更加具体,假设我们有一个类CompanyZ使用加密的方式进行通信:
class CompanyZ { // this class offers no public: // sendCleartext function ... void sendEncrypted(const std::string& msg); ... };
普通的MsgSender模板对于CompanyZ来说是不合适的,因为普通模板提供了一个对于CompanyZ对象来说没有意义的函数。为了改正这个问题,我们能够为CompanyZ创建一个MsgSender的特化版本:
template<> // a total specialization of class MsgSender<CompanyZ> { // MsgSender; the same as the public: // general template, except
... // sendClear is omitted
void sendSecret(const MsgInfo& info)
{ ... }
注意在类定义开始的地方出现的”template<>” 语法。它表明这既不是模板也不是单独的类。它是当使用CompanyZ作为模板参数时,会使用到的MsgSender模板的特化版本。这叫做模板全特化(total template specialization):模板MsgSender为类型CompanyZ进行了特化,并且特化是全特化——一旦类型参数被定义为ComanyZ,模板参数的其它地方就不会再发生变化。
在MsgSender已经有了CompanyZ的特化版本的情况下,再看一下派生类LoggingMsgSender:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
write "before sending" info to the log;
sendClear(info); // if Company == CompanyZ,
// this function doesn’t exist!
write "after sending" info to the log;
}
...
};
正如注释中所写的,当基类是MsgSender<CompanyZ>的情况下这段代码没有意义,因为基类中没有提供sendClear函数。这也是C++拒绝这个调用的原因:它认识到基类模板可能被特化了,但是特化版本并没有提供普通模板中的一般接口。因此,它拒绝在模板化基类中寻找继承而来的名字。从某种意义上讲,当我们从面向对象C++转到模板C++的时候(Item 1),继承就会停止工作。
3. 如何解决问题——三种方法
如果让其重新工作,我们必须让C++“不在模板化基类中寻找“的行为失效。有三种方法达到这个目标。
第一,调用基类函数时你可以为其加上”this->“:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
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;
}
...
};
第二,你可以使用using声明,你可能会熟悉,因为Item 33中用了类似的解决方案。那个条款中解释了如何使用using声明来将隐藏起来的基类名字带入派生类作用域。我们于是可以像下面这种方式实现sendClearMsg:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear; // tell compilers to assume
... // that sendClear is in the
// base class
void sendClearMsg(const MsgInfo& info)
{
... sendClear(info); // okay, assumes that ... // sendClear will be inherited } ... };
最后,让你的代码通过编译的方法是在基类中明确指出需要调用的函数:
template<typename Company> class LoggingMsgSender: public MsgSender<Company> { public: ... void sendClearMsg(const MsgInfo& info) { ... MsgSender<Company>::sendClear(info); // okay, assumes that ... // sendClear will be
} // inherited
...
};
这基本上会是你最不愿意使用的解决这个问题的方法,因为如果被调用的函数是virtual的,显示的限定符会关掉virtual绑定行为。
从名字可见性的观点来看,每个方法都做了同样的事情:它向编译器许诺,接下来的任何基类模板特化都会支持一般模板提供的接口。这样的许诺是当所有的编译器解析一个像LoggingMsgSender的派生类模板的时候所需要的,但是如果这个许诺并没有兑现,在接下来的编译中真理就会浮现。例如,如果下面的源码有这种情况:
LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
... // put info in msgData zMsgSender.sendClearMsg(msgData); // error! won’t compile
对sendClearMsg的编译不会通过,因为从这个点上来说,编译器知道基类是模板的特化版本MsgSender<CompanyZ>,并且它们知道这个类没有提供sendClearMsg想要调用的sendClear函数。
4. 本条款讨论的根本所在
从根本上来说,这个问题是编译器对基类成员的无效引用进行诊断的早(当派生类模板被解析的时候)或晚(当这些模板用特定的模板参数进行实例化的时候)的问题。C++的方针是更加喜欢早点诊断,这也是为什么当类从模板中特化的时候,它假定对基类的内容一无所知。
5. 总结
在派生类模板中,引用基类模板中的名字可以使用“->this“前缀,通过使用using声明,或者通过使用显示的使用基类限定符。
读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字的更多相关文章
- Effective C++ -----条款43:学习处理模板化基类内的名称
可在derived class templates内通过“this->“指涉base class templates内的成员名称,或藉由一个明白写出的”base class资格修饰符”完成.
- 读书笔记 effective c++ Item 45 使用成员函数模板来接受“所有兼容类型”
智能指针的行为像是指针,但是没有提供加的功能.例如,Item 13中解释了如何使用标准auto_ptr和tr1::shared_ptr指针在正确的时间自动删除堆上的资源.STL容器中的迭代器基本上都是 ...
- 读书笔记 effective C++ Item 33 避免隐藏继承而来的名字
1. 普通作用域中的隐藏 名字实际上和继承没有关系.有关系的是作用域.我们都知道像下面的代码: int x; // global variable void someFunc() { double x ...
- 读书笔记 effective C++ Item 40 明智而谨慎的使用多继承
1. 多继承的两个阵营 当我们谈论到多继承(MI)的时候,C++委员会被分为两个基本阵营.一个阵营相信如果单继承是好的C++性质,那么多继承肯定会更好.另外一个阵营则争辩道单继承诚然是好的,但多继承太 ...
- 读书笔记 effective c++ Item 44 将与模板参数无关的代码抽离出来
1. 使用模板可能导致代码膨胀 使用模板是节省时间和避免代码重用的很好的方法.你不需要手动输入20个相同的类名,每个类有15个成员函数,相反,你只需要输入一个类模板,然后让编译器来为你实例化20个特定 ...
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
- 读书笔记 effective c++ Item 31 把文件之间的编译依赖降到最低
1. 牵一发而动全身 现在开始进入你的C++程序,你对你的类实现做了一个很小的改动.注意,不是接口,只是实现:一个私有的stuff.然后你需要rebuild你的程序,计算着这个build应该几秒钟就足 ...
- 读书笔记 effective c++ Item 34 区分接口继承和实现继承
看上去最为简单的(public)继承的概念由两个单独部分组成:函数接口的继承和函数模板继承.这两种继承之间的区别同本书介绍部分讨论的函数声明和函数定义之间的区别完全对应. 1. 类函数的三种实现 作为 ...
- 读书笔记 effective c++ Item 35 考虑虚函数的替代者
1. 突破思维——不要将思维限定在面向对象方法上 你正在制作一个视频游戏,你正在为游戏中的人物设计一个类继承体系.你的游戏处在农耕时代,人类很容易受伤或者说健康度降低.因此你决定为其提供一个成员函数, ...
随机推荐
- mysql常用语法
创建表 create table <表名>( <字段名> 类型(长度) not null primary key auto_increment, **主键 name char ...
- JSON - 使用cJSON 解析Qt通过UDP发送的JSON数据
1,cJSON支持在C程序中创建和解析JSON数据,其提供多种方法供C程序使用,最直接的是将cJSON.c和cJSON.h加入到C工程中,源代码:https://github.com/DaveGamb ...
- Android Weekly Notes Issue #245
Android Weekly Issue #245 February 19th, 2017 Android Weekly Issue #245 本期内容: 写好单元测试的几条原则; 如何mock Ko ...
- jQuery_第二章_定时器
- Android的开机流程
Android的开机流程 1. 系统引导bootloader 1) 源码:bootable/bootloader/* 2) 说明:加电后,CPU将先执行bootloader程序,此处有三种选择 a) ...
- 阿里云LINUX服务器配置HTTPS(NGINX)
本文首发于:http://www.fengzheng.pub/archives/238.html 背景说明 服务器为阿里云 ECS,操作系统为 CentOS 6.5. 部署配置说明 第一步,安装ngi ...
- 【译】C++日志(Logging in C++)
声明:原创翻译,转载请注明出处!http://www.cnblogs.com/mewmicro/p/6432507.html 注:作者Petru Marginean,2007/09/05 日志记录 ...
- java集合框架05——ArrayList和LinkedList的区别
前面已经学习完了List部分的源码,主要是ArrayList和LinkedList两部分内容,这一节主要总结下List部分的内容. List概括 先来回顾一下List在Collection中的的框架图 ...
- angular 1.26 版本 window.history.back() 自动去顶部
在1.26版本,在url ("www.example.com#xx"),接着按back,会自动调到顶部,这是因为angular的默认设置 只要在config注入$AnchorScr ...
- VUE2.0实现购物车和地址选配功能学习第一节(来源--慕课网河畔一角)
第一节 vue知识 vue-resource:和后台交互的一个插件,实现get.post和jsonp等功能.(替代jQuery) vue特点: 1.易用:通过创建vue实例,{{}}绑定数据十分方便 ...