MoreEffectiveC++Item35 条款25 将constructor和non-member functions虚化
1.virtual constructor
在语法上是不可将构造函数声明成虚函数,虚函数用于实现"因类型而异的行为",也就是根据指针或引用所绑定对象的动态类型而调用不同实体.现在所涉及的
virtual-constructor实际上是"仿virtual-constructor.
假设你设计一个软件,用来处理新闻事件,它由文字和图形构成
class NLComponent { //用于 newsletter components
public: // 的抽象基类
... //包含至少一个纯虚函数
};
class TextBlock: public NLComponent {
public:
... // 不包含纯虚函数
};
class Graphic: public NLComponent {
public:
... // 不包含纯虚函数
};
class NewsLetter { // 一个 newsletter 对象
public: // 由 NLComponent 对象
... // 的链表组成
private:
list<NLComponent*> components;
};
这些class的彼此关系如下图
NewsLetter可能存储于磁盘中,为了从磁盘中读取NewsLetter对象到内存中,它可能有一个接受istream&参数的构造函数:
class NewsLetter
{
public:
NewsLetter(istream& str);
...
};
//此构造函数的伪代码是这样的:
NewsLetter::NewsLetter(istream& str)
{
while (str)
{
//从 str 读取下一个 component 对象;
//把对象加入到 newsletter 的 components 对象的链表中去;
}
}
NewsLetter(istream& str)又可以将其主要功能交由一个readCompnent函数实现:
class NewsLetter {
public:
NewsLetter(istream& str);
...
private:
//从str读取下一个NLCompnent的数据,产生组件(compnent),并返回一个指针指向它
static NLCompnent* readCompnent(istream& str);
list<NLCompnent*> compnents;
...
};
NewsLetter::NewsLetter(istream& str){
while (str) {
//将readCompnent返回的指针加到compnents list尾端
compnents.push_back(readCompnent(str));
}
}
函数readCompnent产生一个新的NLCompnent子类对象(TextBlock或Graphic),并返回一个NLCompnent指针,由于它能够产生不同类型对象,因此称它为一个virtual constructor.Virtual constructor在很多情况下都很有用,其中之一就是从磁盘(或网络或磁带)读取对象信息.
2.virtual copy constructor
一种特殊的virtual constructor——copy virtual constructor,它返回一个指针指向其调用者的新副本,指针的动态类型由调用它的对象的类型决定.TextBlock和Graphic的copy virtual constructor的可以像这样:
class NLComponent {
public:
// 声明virtual copy constructor
virtual NLComponent * clone() const = ;
...
};
class TextBlock: public NLComponent {
public:
virtual TextBlock * clone() const // virtual copy constructor
{ return new TextBlock(*this); }
...
};
class Graphic: public NLComponent {
public:
virtual Graphic * clone() const // virtual copy constructor
{ return new Graphic(*this); }
...
};
这样就clone其实就是实现virtual copy constructor
虽然derived class重新定义base class的虚函数时,但是声明返回值得类型(父类返回指针或引用,那派生类中也返回指针或引用)必须要与base class中相同,但如果函数返回类型是指向base class的指针(或引用),那么derived class可以返回指向其derived class的指针(或引用).因此以上clone虽然是虚函数,但其返回的指针类型可以不同
下面我们实现NewsLetter实现(正常的)copy constructor:
class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs);
...
private:
list<NLComponent*> components;
};
NewsLetter::NewsLetter(const NewsLetter& rhs){
//遍历rhs的list,运用每个元素的virtual copy constructor将元素复制到此对象的compnents list中.
for (list<NLComponent*>::const_iterator it =rhs.components.begin();it != rhs.components.end();++it)
//it指向rhs.compnents的目前元素,调用该元素的clone函数取得一个副本并加到本对象的compnents list的尾端
components.push_back((*it)->clone());
}
3.Non-Member Functions 的行为虚化
正如constructors无法被虚化,non-member function原则上也无法被虚化——它连成员函数都不是.考虑要为TextBlock和Graphic实现<<操作符,要使<<对TextBlock和Graphic实现不同的行为,直接的思路就是将<<虚化,但实际上这无法实现:<<的第一个操作数是ostream&,也就是说<<要作为member function,就只能成为ostream类的成员,但这是不可能的.因此<<只能为non-member函数,这是可以采取和virtual constructor类似的策略实现virtual non-member function
class NLComponent {
public:
virtual ostream& print(ostream& s) const = ;
...
};
class TextBlock: public NLComponent { bbs.theithome.com
public:
virtual ostream& print(ostream& s) const;
...
};
class Graphic: public NLComponent {
public:
virtual ostream& print(ostream& s) const;
...
};
inline ostream& operator<<(ostream& s, const NLComponent& c){
return c.print(s);
}
小结:
构造函数的虚化: 可以使用使用拷贝构造函数,通过返回不同的派生类的指针来模拟出构造不同的对象.
没有成员变量的函数的虚化: 用一个虚函数来封装想要虚化的函数.
MoreEffectiveC++Item35 条款25 将constructor和non-member functions虚化的更多相关文章
- MoreEffectiveC++Item35 条款27: 要求或禁止对象产生于heap中
一 要求对象产生在heap中 阻止对象产生产生在non-heap中最简单的方法是将其构造或析构函数声明在private下,用一个public的函数去调用起构造和析构函数 class UPNumber ...
- MoreEffectiveC++Item35 条款26: 限制某个class所能产生的对象个数
一 允许零个或一个对象 我们知道每当即将产生一个对象,我们有一个constructor被调用,那么我们现在想组织某个对象的产生,最简单的方法就是将其构造函数声明成private(这样做同事防止了这个类 ...
- MoreEffectiveC++Item35(效率)(条款16-24)
条款16 谨记80-20法则 条款17 考虑使用 lazy evaluation(缓释评估) 条款18 分期摊还预期的计算成本 条款19 了解临时对象的来源 条款20 协助完成"返回值的优化 ...
- MoreEffectiveC++Item35(异常)(条款9-15)
条款9 使用析构函数防止内存泄漏 条款10 在构造函数中防止内存泄漏 条款11 禁止异常信息传递到析构函数外 条款12 理解"抛出一个异常''与"传递一个参数"或调用一个 ...
- MoreEffectiveC++Item35(操作符)(条款5-8)
条款5 对定制的"类型转换函数"保持警惕 条款6 区别increment/decrement操作符的前值和后置形式 条款7 千万不要重载&&,||,和,操作符 条款 ...
- MoreEffectiveC++Item35(基础议题)(条款1-4)
条款1:区别指针和引用 条款2:最好使用C++转换操作符 条款3: 绝对不要以多态的方式处理数组 条款4: 避免无用的缺省构造函数 条款1:区别指针和引用 1.指针(pointer) 使用[*/-&g ...
- Effective C++:条款25:考虑写出一个不抛异常的swap函数
(一) 缺省情况下swap动作可由标准程序库提供的swap算法完毕: namespace std { template<typename T> void swap(T& a, T& ...
- effective C++ 条款25 swap
item 25:一个不抛异常的swap函数 标准库有一个swap用于交换两个对象值 namespace std{ template<typename T> void swap(T& ...
- Effective C++ -----条款25:考虑写出一个不抛异常的swap函数
当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常. 如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于cla ...
随机推荐
- Python笔记 #02# Inner workings of lists
源:DataCamp datacamp 的 DAILY PRACTICE + 日常收集. List of lists Subset and conquer Slicing and dicing Li ...
- OpenStack之Nova模块
Nova简介 nova和swift是openstack最早的两个组件,nova分为控制节点和计算节点,计算节点通过nova computer进行虚拟机创建,通过libvirt调用kvm创建虚拟机,no ...
- # 20145103《Java程序设计》第6周学习总结
20145103<Java程序设计>第6周学习总结 教材学习内容总结 第十章 第十章输入和输出 10.1.1 ·若要将数据从来源中取出,可以使用输入串流:若要将数据写入目的地,可以使用输出 ...
- 彻底搞懂hashCode与equals的作用与区别及应当注意的细节
以前写程序一直没有注意hashCode的作用,一般都是覆盖了equals,缺没有覆盖hashCode,现在发现这是埋下了很多潜在的Bug!今天就来说一说hashCode和equals的作用. 先来试想 ...
- gerrit代码审核工具之“error unpack failed error Missing unknown”错误解决思路
使用gerrit代码审核工具时遇到error: unpack failed: error Missing unknown d6d7c89bd1d77f44c5c8e99437aaffbfc0684e7 ...
- 1_jenkins环境搭建
前言 为什么要使用jenkins 可以实现批量部署.管理 支持常见的版本控制工具,git, svn等 发展成熟,使用范围广,容易找到解决方案 插件丰富,可以满足自己的需求 什么是jenkins 简单的 ...
- optind变量
1.这个变量是在什么地方定义的? 答:系统定义的 2.这个变量在什么场景下使用? 答:在解析命令行参数时会用到 3.这个变量存在的意义? 在每调用一次getopt()或getopt_long()类似函 ...
- struts1.2上传多个文件
页面: <input type="file" name="impFile[0]" style="wid ...
- 【Network Architecture】Feature Pyramid Networks for Object Detection(FPN)论文解析(转)
目录 0. 前言 1. 博客一 2.. 博客二 0. 前言 这篇论文提出了一种新的特征融合方式来解决多尺度问题, 感觉挺有创新性的, 如果需要与其他网络进行拼接,还是需要再回到原文看一下细节.这里 ...
- C#显示接口实现和隐式接口实现
在项目中可能会遇到显示接口实现和隐式接口实现.什么意思呢?简单来说使用接口名作为方法名的前缀,这称为“显式接口实现”:传统的实现方式,称为“隐式接口实现”.隐式接口实现如下: interface IS ...