研究了一波RTTI,再介绍软件开发的201个原则

最近研究了一波RTTI,整理了一下知识点,在这里分享一下,下面是目录:

RTTI 是 Run Time Type Information 的缩写,从字面上来理解就是运行时期的类型信息,它的主要作用就是动态判断运行时期的类型。

一般在dynamic_cast和typeid中用到,例如父类B的指针转换子类A的指针,dynamic_cast会判断B究竟是不是A的父类,如果不是,会返回nullptr,相对于强转会更加安全。依据什么判断的呢?就是RTTI。

先看下面这段代码:

#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
    int a;
    int b;
    Base()
    {
        cout << this << " Base \n";
    }
    virtual void func()
{
        cout << this << " hello Base \n";
    };
    void basefunc()
{
        cout << this << " hello basefunc \n";
    }
};
class BaseBB
{
public:
    int d;
    int c;
    BaseBB()
    {
        cout << this << " BaseBB \n";
    }
    virtual void func()
{
        cout << this << " hello BaseBB \n";
    }
};
class Derive : public Base
{
public:
    Derive()
    {
        cout << this << " Derive \n";
    }
    void func() override
{
        cout << this << " hello Derive \n";
    }
};
int main()
{
    Derive *d = new Derive;
    typeid(d);
    d->func();
    Base *b = static_cast<Base *>(d);
    b->func();
    b->basefunc();
    Derive *b1 = dynamic_cast<Derive *>(b);
    Derive *b2 = static_cast<Derive *>(b);
    b1->func();
    b2->func();
    BaseBB *b3 = dynamic_cast<BaseBB *>(b);
    BaseBB *b4 = reinterpret_cast<BaseBB *>(b);
    cout << d << " " << b << " " << b1 << " " << b2 << " " << b3 << " " << b4 << endl;
    return 0;
}

结果如下:

clang++ test_rtti.cc -std=c++11;./a.out

0x7fe80ac05920 Base 
0x7fe80ac05920 Derive 
0x7fe80ac05920 hello Derive 
0x7fe80ac05920 hello Derive 
0x7fe80ac05920 hello basefunc 
0x7fe80ac05920 hello Derive 
0x7fe80ac05920 hello Derive 
0x7fe80ac05920 0x7fe80ac05920 0x7fe80ac05920 0x7fe80ac05920 0x0 0x7fe80ac05920

上面的代码是正常的一段使用多态的代码,同时也包含了子类指针转基类指针,基类指针转子类指针,从输出结果中可以看到,使用dynamic_cast进行不合理的基类子类指针转换时,会返回nullptr,而强转则不会返回nullptr,运行时肯定就会出现奇奇怪怪的错误,比较难排查。

如果在编译时加上-fno-rtti会怎么样?结果是这样:

clang++ test_rtti.cc -std=c++11 -fno-rtti

test_rtti.cc:60:5: error: use of typeid requires -frtti
    typeid(d);
    ^
test_rtti.cc:65:18: error: use of dynamic_cast requires -frtti
    Derive *b1 = dynamic_cast<Derive *>(b);
                 ^
test_rtti.cc:69:18: error: use of dynamic_cast requires -frtti
    BaseBB *b3 = dynamic_cast<BaseBB *>(b);
                 ^
3 errors generated.

可以看到,加上了-fno-rtti编译时,使用typeid或dynamic_cast会报错,即添加-fno-rtti编译会禁止我们使用dynamic_cast和typeid。那为什么要禁止使用他们呢?

1. RTTI的空间成本非常高:每个带有vtable(至少一个虚拟方法)的类都将获得RTTI信息,其中包括类的名称及其基类的信息。此信息用于实现typeid运算符以及dynamic_cast。(大小问题大家可以自己编写代码验证一下)

2. 速度慢,运行时多判断了一层,性能肯定更慢一些。

tips:我这里又将typeid和dynamic_cast去掉重新编译,结果表明添加了-fno-rtti,还是可以正常使用多态,所以大家不用担心rtti的禁用会影响多态的使用。

都知道RTTI信息是存在于虚函数表中,而添加-fno-rtti后代表禁止了RTTI,那虚函数表中还会有rtti信息吗?

我这里使用clang的命令查看一下虚函数表:

clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -fno-rtti -c test_rtti.cc

test_rtti.cc:51:17: warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
    void func() override
                ^
Original map
 void Derive::func() -> void Base::func()
Vtable for 'Derive' (3 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (Base, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | void Derive::func()

VTable indices for 'Derive' (1 entries).
   0 | void Derive::func()

通过结果可以看到,即使添加了-fno-rtti,虚函数表中还是会存在RTTI指针,但是我查看很多文档都说rtti会导致可执行文件的体积增大一些(毕竟-fno-rtti最大的目的就是为了减小代码和可执行文件的大小),所以我估计指针指向的块里面可能什么信息都没有,具体就不得而知了。

再稍微介绍下软件开发的基本原则,以下内容摘自《软件开发的201个原则》:

关于软件质量,业界普遍认为有3个决定性要素:人、过程和工具。如何基于这些要素提升代码的质量和开发效率,是软件工程研究者和实践者一直在努力的方向。

不同的公司有不同的文化背景,虽然开发不同的软件项目有不同的实践过程,但所要遵守的基本原则都是一样的。大量实践证明——

了解软件开发基本原则的工程师,比那些不了解基本原则的,编写代码的质量和开发效率明显胜出一筹。

原则 1 质量第一QUALITY IS #1

无论如何定义质量,客户都不会容忍低质量的产品。质量必须被量化,并建立可落地实施的机制,以促进和激励质量目标的达成。即使质量没达到要求,也要按时交付产品,这似乎是政治正确的行为,但这是短视的。从中长期来看,这样做是自杀。质量必须被放在首位,没有可商量的余地。Edward Yourdon 建议,当你被要求加快测试、忽视剩余的少量 bug、在设计或需求达成一致前就开始编码时,要直接说“不”。

原则 7 尽早把产品交给客户GIVE PRODUCTS TO CUSTOMERS EARLY

在需求阶段,无论你多么努力地试图去了解客户的需求,都不如给他们一个产品,让他们使用它,这是确定他们真实需求的最有效方法。如果遵循传统的瀑布式开发模型,那么在 99% 的开发资源已经耗尽之后,才会第一次向客户交付产品。如此一来,大部分的客户需求反馈将发生在资源耗尽之后。和以上方法相反,可在开发过程的早期构建一个快速而粗糙的原型。将这个原型交付给客户,收集反馈,然后编写需求规格说明并进行正规的开发。使用这种方法,当客户体验到产品的第一个版本时,只消耗了 5%~20% 的开发资源。如果原型包含合适的功能,就可以更好地理解和把握最有风险的客户需求,最终产品也就更有可能让客户满意。这有助于确保将剩余的资源用于开发正确的系统。

原则 17 只要可能,购买而非开发IF POSSIBLE, BUY INSTEAD OF BUILD

要降低不断上涨的软件开发成本和风险,最有效的方法就是,购买现成的软件,而不是自己从头开发。确实,现成的软件也许只能解决 75% 的问题。但考虑一下从头开发的选择吧:支付至少 10 倍于购买软件的费用,且要冒着超出预算 100% 且延期的风险(如果最后能够完成!),并且最终发现,它只能满足 75% 的预期。对一个客户来说,新的软件开发项目似乎最初总是令人兴奋的。开发团队也是“乐观的”,对“最终”解决方案充满了希望。但几乎很少有软件开发项目能够顺利运行。不断增加的成本通常会导致需求被缩减,最终研发出的软件可以满足的需求也许跟现成的软件差不多。作为一个开发者,应该复用尽可能多的软件。复用是“购买而非开发”原则在较小范围内的体现。

原则 22 技术优先于工具TECHNIQUE BEFORE TOOLS

一个没规矩的木匠使用了强大的工具,会变成一个危险的没规矩的木匠。一个没规矩的软件工程师使用了工具,会变成一个危险的没规矩的软件工程师。在使用工具前,你应该先要“有规矩”(即理解并遵循适当的软件开发方法)。当然,你也要了解如何使用工具,但这和“有规矩”相比是第二位的。我强烈建议,在投资于工具以对某项技术“自动化”之前,先手工验证这项技术,并说服自己和管理层:这项技术是可行的。在大多数情况下,如果一项技术在手工操作时不灵,那么在自动操作时也不灵。

原则 37 要承担责任TAKE RESPONSIBILITY

在所有工程学科中,如果一个设计失败,工程师会受到责备。因此,当一座大桥倒塌时,我们会问“工程师哪里做错了?”当一个软件失败了,工程师很少受到责备。如果他们被责备了,他们会回答,“肯定是编译器出错了”,或“我只是按照指定方法的 15 个步骤做的”,或“我的经理让我这么干的”,或“计划剩余的时间不够”。事实是,在任何工程学科中,用最好的方法也可能产出糟糕的设计,用最过时的方法也可能做出精致的设计。不要有任何借口。如果你是一个系统的开发者,把它做好是你的责任。要承担这个责任。要么做好,要么就压根不做。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序喵大人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值