百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

C++|资源获取即初始化RAII与运行时类型识别RTTI

liebian365 2025-01-23 18:33 13 浏览 0 评论

C++在兼容C(procedural programming)的基础上实现了Object-oriented programming的编程范式,与object-oriented programming相关的三个核心思想就是封装、继承、多态。

C++是没有GC(Garbage Collection)的语法机制的。对于动态内存的管理(如何避免泄露,特别是引发异常时),RAII(Resource Acquisition Is Initialization,资源获取就是初始化)就是指利用类的封装和对象的自动构造和自动析构的语法机制来实现对资源的有效管理(初始化与释放)。

Object-oriented programming的核心思想是“organizing types into hierarchies to express their relationships directly in code”(将类型组织到层次结构中,以直接在代码中表示它们之间的关系)。继承就是一种纵向的“is-a“关系。在继承的层次关系中,通过虚函数来实现多态。与此同时,虚函数的语法机制中还实现了简单的的RTTI(Run Time Type Identification,运行时类型识别)。

1 RAII(Resource Acquisition Is Initialization)

RAII是C++语言的一种管理资源(如堆内存、文件句柄、网络连接套接字等)、避免泄漏的语法机制。C++保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。

C++社群在赋予技术神秘的缩略语和古怪的名字方面有着悠久而自豪的传统。RAII就是一个例子,它既古怪又神秘。RAII表示“资源获取即初始化"(resource acquisition is initializa-tion), 而不是某些人会以为的“初始化即资源获取"(initialization is resource acquisition)。 一句闲话,如果你打算搞怪,那就干脆怪到底,否则达不到效果。

RAII是一项很简单的技术,利用C++对象生命期的概念来控制程序的资源。RAII的基本技术原理很简单。 如果希望对某个重要资源进行跟踪,那么创建一个对象,并将资源的生命期和对象的生命期相关联。如此一来,就可以利用C++复杂老练的对象管理设施来管理资源。最简单的 RAII形式是,创建这样的一个对象,使其构造函数获取一份资源,而析构函数则释放这份资源:

class Resource {} ;
class ResourceHandle {
public:
    
    explicit ResourceHandle( Resource *aResource )
        : r_(aResource) {}  //获取资源
    ~ResourceHandle ()
    { delete r_;}           //释放资源
    Resource *get()
    { return r_;}           //访问资源
private:
    ResourceHandle( const ResourceHandle &);
    ResourceHandle &operator = ( const ResourceHandle &) ;
    Resource *r_;
} ;

2 RTTI(Run Time Type Identification)

看如下范例:

Human *phuman = new Men(); // 基类指针指向一个派生类对象
Human &r = *phuman;             // 基类引用绑定到派生类对象上

这里的静态类型就是变量声明时的类型,静态类型在编译时就是已知的,如上面代码中的phuman、r,它们的静态类型就是Human类型指针和Human类型引用。

当基类的指针或引用指向派生类对象,会出现只有在运行的时候(执行到这行代码的时候)才能确定的类型型态称为动态类型。在C++中,当基类存在虚函数,基类的指针或引用指向派生类对象,产生多态时才会出现动态类型。如果不是多态的上下文,则静态类型与动态类型应该永远都是一致的。

在C++ 环境中﹐头文件(header file) 含有类之定义(class definition)亦即包含有关类的结构信息(representational information)。但是﹐这些信息只供编译器(compiler)使用﹐编译完毕后并未留下来﹐所以在执行时期(at run-time) ﹐无法得知对象的类信息﹐包括类名称、数据成员名称与类型、函数名称与类型等等。例如﹐两个类Figure和Circle﹐互相之间是继承关系。

若有如下指令﹕

Figure *p;
p = new Circle();
Figure &q = *p;

在执行时﹐p指向一个对象﹐但欲得知此对象之类信息﹐就有困难了。同样欲得知q 所参考(reference) 对象的类信息﹐也无法得到。RTTI(Run-Time Type Identification)就是要解决这困难﹐也就是在执行时﹐您想知道指针所指到或参考到的对象类型时﹐该对象有能力来告诉您。随着应用场合之不同﹐所需支持的RTTI范围也不同。最单纯的RTTI包括﹕

I 类识别(class identification)──包括类名称或ID。

II 继承关系(inheritance relationship)──支持执行时期的「往下变换类型」(downward casting)﹐亦即动态变换类型(dynamic casting) 。

在对象数据库存取上﹐还需要下述RTTI﹕

I 对象结构(object layout) ──包括属性的类型、名称及其位置(position或offset)。

II 成员函数表(table of functions)──包括函数的类型、名称、及其参数类型等。

其目的是协助对象的I/O 和持久化(persistence) ﹐也提供调试信息等。

若依照Bjarne Stroustrup 之建议,C++ 还应包括更完整的RTTI﹕

I 能得知类所实例化的各对象 。

II 能参考到函数的源代码。

III 能取得类的有关在线说明(on-line documentation) 。

其实这些都是C++ 编译完成时所丢弃的信息﹐如今只是希望寻找一个途径来将之保留到执行期间。然而﹐要提供完整的RTTI﹐将会大幅提高C++ 的复杂度。

在C++中,最单纯的RTTI使用了两个运算符:

dynamic_cast,将父类的指针或引用安全地转换为子类的指针或引用。

typeid:返回指针或者引用所指对象的实际类型。

特别需要注意的是,上述两个运算符要能够正常地如所期望的那样工作,父类中至少要有一个虚函数,不然这两个运算符工作的结果很可能与预期的不一样。因为只有虚函数的存在,这两个运算符才会使用指针或引用所指对象的类型(new时的类型)。

Java中任何一个类都可以通过反射机制来获取类的基本信息(接口、父类、方法、属性、Annotation等),而且Java中还提供了关键字instanceof,可以在运行时判断一个类是不是另一个类的子类或者是该类的对象,Java可以生成字节码文件,再由JVM(Java虚拟机)加载运行,字节码文件中可以含有类的信息。

在C++ 程序中﹐若类含有虚函数﹐则该类会有个虚函数表(Virtual Function Table﹐ 简称VFT )。为了提供RTTI﹐C++ 就将在VFT 中附加个指针﹐指向typeinfo对象﹐这对象内含RTTI资料。

由于该类所实例化之各对象﹐皆含有个指针指向VFT 表﹐因之各对象皆可取出typeinfo对象而得到RTTI。例如﹐

Figure *f1 = new Square();
Figure *f2 = new Square();
const typeinfo ty = typeid(*f2);

其中﹐typeid(*f2) 的动作是﹕

I 取得f2所指之对象。

II 从对象取出指向VFT 之指针﹐经由此指针取得VFT 表。

III 从表中找出指向typeinfo对象之指针﹐经由此指针取得typeinfo对象。

这typeinfo对象就含有RTTI了。经由f1及f2两指针皆可取得typeinfo对象﹐所以 typeid(*f2) == typeid(*f1)。

对于C++的多态,如果父类中有一个虚函数,并且在子类中覆盖了这个虚函数,那么,当父类指针指向子类对象的时候,调用的虚函数就是子类里的虚函数。如下面实例中的vt()函数。

也是可以通过RTTI来实现同样的功能,如下面实例中的vt2RTTI()函数。

如果子类中有一个父类中没有的普通成员函数(非虚函数),那么,即使是父类指针指向了该子类对象,但也没办法用父类指针调用子类是的这个普通成员函数。那么如果就想用父类指针调用子类中的这个普通成员函数,该怎样做呢?

I 把这个普通成员函数改写成虚函数(在父类中只要是虚函数,在子类中自然就是虚函数)。但是,如果子类中每增加一个新成员函数就要在父类中增加等同的虚函数,这样的解决方案怎是让人不太满意。

II 替代虚函数方案的办法就是RTTI,使用dynamic_cast进行类型转换。如下面实例中的 doSomething()函数:

#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;
 
class Movable
{
public:
    virtual void move()=0;
};
 
class Bus : public Movable
{
public:
    virtual void move()
    {
        cout << "Bus -- move" << endl;
    }
    
    void carry()
    {
        cout << "Bus -- carry" << endl;
    }
};
 
class Tank :public Movable
{
public:
    virtual void move()
    {
        cout << "Tank -- move" << endl;
    }
 
    void fire()
    {
        cout << "Tank -- fire" << endl;
    }
};
 
void vt2RTTI(Movable *obj)
{
 
    if(typeid(*obj) == typeid(Bus))  // 如果去掉move()的虚函数声明也无法RTTI
    {
        Bus *bus = dynamic_cast<Bus*>(obj);//注意这两个语句
        bus->move();
    }
 
    if(typeid(*obj) == typeid(Tank))
    {
        Tank *tank=dynamic_cast<Tank*>(obj);//注意这两个语句
        tank->move();
    }
}

void vt(Movable *obj)
{
    obj->move(); 
}

void doSomething(Movable *obj)
{
    if(typeid(*obj) == typeid(Bus))  // 如果去掉move()的虚函数声明也无法RTTI
    {
        Bus *bus = dynamic_cast<Bus*>(obj);//注意这两个语句
        bus->carry();
    }
 
    if(typeid(*obj) == typeid(Tank))
    {
        Tank *tank=dynamic_cast<Tank*>(obj);//注意这两个语句
        tank->fire();
    }
}

int main(void)
{
    Bus b;
    Tank t;
    cout<<typeid(b).name()<<endl;
    cout<<typeid(t).name()<<endl;
    vt2RTTI(&b);
    vt2RTTI(&t);
    vt(&b);
    vt(&t);
    doSomething(&b);
    doSomething(&b);
    return 0;
}
/*
3Bus
4Tank
Bus -- move
Tank -- move
Bus -- move
Tank -- move
Bus -- carry
Tank -- fire
*/

虽然vt()和vt2RTTI()皆能达到多态化(polymorphism) ﹐但使用复选方法的vt2RTTI()﹐常导致违反著名的「开放╱封闭原则」(open/closed principle)。反之﹐使用虚函数方法的vt()则可合乎这个原则。

当Movable类体系再派生出子类时﹐vt2RTTI() 函数的内容必须多加个if指令。因而违反了「开放╱封闭原则」。

想一想,如果C++ 并未提供RTTI﹐则程序员毫无选择必须使用虚函数来支持move() 函数的多态,使用vt()函数:

void vt(Movable *obj)
{
    obj->move(); 
}

如此﹐Movable类体系能随时派生类﹐而不必修正vt() 函数。亦即﹐Movable体系有个稳定的接口(interface) ﹐vt() 使用这接口﹐使得vt() 函数也稳定﹐不会随Movable类体系的扩充而变动。这是封闭的一面。而这稳定的接口并未限制Movable体系的成长﹐这是开放的一面。因而合乎「开放╱封闭」原则﹐软件的结构会更具弹性﹐更易于随环境而不断成长。

ref


https://wwuhn.github.io/witisoPC/34ccpp/CPP的RTTI观念、使用场合及实现来源.html

-End-

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: