转载请注明出处:http://www.cnblogs.com/inevermore/p/4012079.html

 

RTTI,指的是运行时类型识别技术。

 

先看一个貌似无关的问题:

 

为继承体系重载<<操作符

有下面的一个继承体系,我们需要为其添加输出操作符,应该怎么办:

class Animal
{ }; class Cat : public Animal
{ }; class Dog : public Animal
{ };

我们的第一个办法就是为每个类添加operator<<函数,但是我们也可以尝试这样:

class Animal
{
public:
virtual string toString() const = 0;
}; class Cat : public Animal
{
public:
string toString() const
{
return "Cat";
}
}; class Dog : public Animal
{
string toString() const
{
return "Dog";
}
}; ostream &operator<<(ostream &os, const Animal &a)
{
return os << a.toString();
}

显然我们只提供了一个基类的输出运算符,但是因为const Animal &a的缘故,所有子类都可以放入。那么如果根据不同的类型打印不同的内容呢,我们使用了多态。

这样做的好处是什么?如果采用每个类都提供输出运算符的方案,那么我们必须牢记,每编写一个类就要为其添加一个操作符,这一点是很容易遗忘的,但是如果采用我们上面的代码,我们将toString写为虚函数,这样,我们每次去重新实现toString就可以了。虚函数被遗忘的可能性比<<操作符低很多。

 

为继承体系重载==操作符

 

还是之前的类:

class Animal
{ }; class Cat : public Animal
{ }; class Dog : public Animal
{ };

这次我们需要为其提供==操作符,那么我们应该提供几个?为每个类提供一个显然是不够的,因为继承体系中,派生类对象可以被基类对象引用,所以我们有时候需要将Animal &与Cat &进行比较

bool operator==(const Animal &, const Animal &);
bool operator==(const Cat &, const Cat &);
bool operator==(const Dog &, const Dog &);
bool operator==(const Animal &, const Cat &);
bool operator==(const Cat &, const Dog &);
//.............

看起来,五个还不够。就算只有五个,我们需要逐个实现也是非常恐怖。那么有没有好一些的解决方案呢?我们采用本文第一节的技巧。

只重载一个Animal基类的==操作符

每个类实现equal,这是一个虚函数

 

在继续编写代码之前,我们先搞清楚,两个相同的前提是,他们的类型相同,然后再去比较成员变量。

于是我们给出的解决方案如下:

#include <iostream>
#include <typeinfo>
#include <string>
using namespace std; class Animal
{
public:
virtual bool equal(const Animal &other) const = 0;
}; class Cat : public Animal
{
public:
bool equal(const Animal &other) const
{
//Cat Animal
if(const Cat *pc = dynamic_cast<const Cat*>(&other))
{
return name_ == pc->name_;
}
return false;
} private:
string name_;
}; class Dog : public Animal
{
public:
bool equal(const Animal &other) const
{
//Dog Animal
if(const Dog *pd = dynamic_cast<const Dog*>(&other))
{
return name_ == pd->name_;
}
return false;
}
private:
string name_;
}; bool operator==(const Animal &a, const Animal &b)
{
return typeid(a) == typeid(b) && a.equal(b);
} int main(int argc, char const *argv[])
{ Cat c;
Dog d; Animal *pa = &c;
cout << (*pa == c) << endl;
pa = &d;
cout << (*pa == d) << endl;
return 0;
}

我们先来看<<的重载:

bool operator==(const Animal &a, const Animal &b)
{
return typeid(a) == typeid(b) && a.equal(b);
}

这里利用了&&的短路特性,一旦两个对象的类型不同,那么后面就没有必要比较。

typeid是一个类型识别运算符,如果要识别的类型不是class或者不含有virtual函数,那么typeid指出静态类型。如果class含有虚函数,那么typeid在运行期间识别类型。

对equal的调用,显然使用了动态绑定,总是根据对象的实际类型调用对应的equal版本

然后我们看一下Dog中equal的实现:

bool equal(const Animal &other) const
{
//Cat Animal
if(const Cat *pc = dynamic_cast<const Cat*>(&other))
{
return name_ == pc->name_;
}
return false;
}

 

这里利用了dynamic_cast进行了“向下塑形”,dynamic_cast与static_cast有一些不同:

static_cast发生在编译期间,如果转化不通过,那么编译错误,如果编译无问题,那么转化一定成功。static_cast仍具有一定风险,尤其是向下塑形时,将Base*转化为Derived*时,指针可以转化,但是指针未必指向Derived对象。

dynamic_cast发生在运行期间,用于将Base的指针或者引用转化为派生类的指针或者引用,如果成功,返回正常的指针或引用,如果失败,返回NULL(指针),或者抛出异常(bad_cast)

在<<的重载中,我们保证了equal两个参数的类型相同,那么我们为何还需要在equal中“向下塑形”呢?

equal有可能被单独使用,所以other的类型未必和自己相同。

如果不进行转换,other是无法访问name属性的,因为Animal中没有name。

记住下面的代码:

//pb是Base类型指针
if(Derived *pd = dynamic_cast<Derived*>(pb))
{
//转化成功
}
else
{
//失败
}

这是一种使用dynamic_cast的标准实践

这段代码的逻辑是非常严密的:

pd的生存期只存在于转化成功的情况

无法在dynamic_cast和测试代码之间插入代码

如果使用的是引用,可以这样:

try
{
const Derived &d = dynamic_cast<const Derived&>(b);
//成功
}
catch(bad_cast)
{
//失败处理
}

也可以将引用取地址,然后去转化指针。

 

typeid和dynamic_cast是实现RTTI的主要手段。

 

完毕。

使用RTTI为继承体系编写”==”运算符的更多相关文章

  1. [py]python的继承体系-源码目录结构

    python3安装目录 pip install virtualenv pip install virtualenvwrapper pip install virtualenvwrapper-win m ...

  2. 代码的坏味道(12)——平行继承体系(Parallel Inheritance Hierarchies)

    坏味道--平行继承体系(Parallel Inheritance Hierarchies) 平行继承体系(Parallel Inheritance Hierarchies) 其实是 霰弹式修改(Sho ...

  3. 从基层容器类看万变不离其宗的JAVA继承体系

    以容器类为例子,可以观一叶而知秋,看看以前的前辈们是如何处理各种面向对象思想下的继承体系的.读的源代码越多,就越要总结这个继承关系否则读的多也忘得快. 首先摆上一张图片: 看到这张图很多人就慌了,难道 ...

  4. Scala 深入浅出实战经典 第41讲:List继承体系实现内幕和方法操作源码揭秘

    Scala 深入浅出实战经典 第41讲:List继承体系实现内幕和方法操作源码揭秘 package com.parllay.scala.dataset /** * Created by richard ...

  5. Java基础知识强化之集合框架笔记02:集合的继承体系图解

    1. 集合的继承体系图解:

  6. 浅谈javascript继承体系

    最近做web项目,接触了jquery等框架,虽然使用方便,但是还是想学习下Javascript,今天分享下最近对js原型继承的理解,不足之处欢迎指正. 一.构造器的原型属性与原型对象 刚接触js时通常 ...

  7. ASP.NET MVC:如何提供 Controller 继承体系使用的 ModelBinder?

    背景 Mvc 提供了一种可扩展的模型绑定机制,具体来说就是:将客户端传递的参数按照一定的策略绑定到 action 的参数上,这带来的直接好处就是让 action 的参数支持强类型.一般来说我们有如下方 ...

  8. C++ 继承体系中的名称覆盖

    首先一个简单的样例: int x; int f() { double x; cin >> x; return x; } 在上述代码中.函数f的局部变量x掩盖了全局变量x.这得从 " ...

  9. Logback的继承体系

    今天碰到一个问题,发现控制台日志输出两遍,搜索得知,这个是由于logback继承体系导致的. logback不仅会继承level,也会继承appender,需要注意的是: <root> & ...

随机推荐

  1. quartz 调度

    1.POI文件中导入包      <dependency> <groupId>org.quartz-scheduler</groupId> <artifact ...

  2. 创建型设计模式之工厂模式(Abstract Factory)

    结构            意图         提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类. 适用性     一个系统要独立于它的产品的创建.组合和表示时. 一个系统要由多个 ...

  3. C 随机不重复元素~转

    随机产生不重复元素:如何高效产生m个n范围内的不重复随机数(m<=n) 如何产生不重复的随机数?最容易想到的方法,是逐个产生这些随机数,每产生一个,都跟前面的随机数比较,如果重复,就重新产生.这 ...

  4. 《Linux命令、编辑器与shell编程》第三版 学习笔记---000

    Linux概述 1.具有内核编程接口 2.支持多用户(同时) 3.支持多任务 4.支持安全的分层文件系统 a.标准 b.链接 c.权限 5.shell(命令解释器和编程语言) a.文件名生成(通配符和 ...

  5. java基础练习 12

    public class Twelfth { /*海滩上有一堆桃子,五只猴子来分.第一只猴子把这堆桃子凭据分为五份,多了一个,这只猴子把多的一个扔入海中,拿走了一份.第二只猴子把剩下的桃子又平均分成五 ...

  6. centos7 svn服务器搭建

    系统环境:centos 7.5 yum安装svn yum install subversion -y 创建svn 版本库目录 mkdir -p /var/svn/svnrepos 在版本库目录下创建版 ...

  7. UVA 10369 - Arctic NetWork (求最小生成树)

    题意: 在南极有  N  个科研站,要把这些站用卫星和无线电连接起来,是的任意两个之间都能互相通信,如果其中任意的一个地方安装了卫星,那么就可以和其他安装卫星的互相通信,和距离没有关系,但是安装无线电 ...

  8. 贪心+数学【p3156】 [CQOI2011]分金币 ([HAOI2008]糖果传递)

    题目描述 圆桌上坐着n个人,每人有一定数量的金币,金币总数能被n整除.每个人可以给他左右相邻的人一些金币,最终使得每个人的金币数目相等.你的任务是求出被转手的金币数量的最小值. 分析: 设: 每个人最 ...

  9. l洛谷——P1211 [USACO1.3]牛式 Prime Cryptarithm

    P1211 [USACO1.3]牛式 Prime Cryptarithm 题目描述 下面是一个乘法竖式,如果用我们给定的那n个数字来取代*,可以使式子成立的话,我们就叫这个式子牛式. *** x ** ...

  10. [POI2014]FarmCraft

    题目大意: 一个$n(n\le5\times10^5)$个点的树,每个点有一个权值$c_i$,从$1$出发进行欧拉遍历,每个单位时间移动一条边,记每个点$i$被访问到的时间是$t_i$,问最后$\ma ...