查看原文
其他

研究了一波RTTI,再介绍软件开发的201个原则,文末再送6本书

程序喵大人 程序喵大人 2022-08-22

最近研究了一波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 个步骤做的”,或“我的经理让我这么干的”,或“计划剩余的时间不够”。事实是,在任何工程学科中,用最好的方法也可能产出糟糕的设计,用最过时的方法也可能做出精致的设计。不要有任何借口。如果你是一个系统的开发者,把它做好是你的责任。要承担这个责任。要么做好,要么就压根不做。

福利

送六本书:《软件开发的201个原则》,豆瓣 9.1 分


活动规则:


走心奖:本篇文章留言,我会选出三位走心留言,另外经常留言+分享的同学我会重点考虑


幸运奖:加我好友,12 月 9 号 20:00 我会发一条朋友圈,点赞方式抽奖,送出三本

土豪朋友们,也可以直接购买,购买方式在下方:



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存