加载中...
怎样在c++中实现instanceof?
第1节:代码改善:一个“坑爹”的文字类冒险游戏
第2节:在禁止多重继承的情况下,如何设计“直立智慧猩猩”类?
第3节:C++多线程代码中的“乱序”执行现象
第4节:C++中函数指针有什么作用呢?
第5节:为什么我用c++写的游戏那么简陋?
第6节:多线程读写socket导致的数据混乱的原因是什么?
第7节:WebSocket 是什么原理?为什么可以实现持久连接?
第8节:怎样在c++中实现instanceof?
第9节:一个函数多处 return 是好风格吗?
第10节:C++中虚函数相比非虚函数的优势
第11节:为什么 C::C::C::C::foo() 能编译成功?
第12节:如何静态反射C++枚举的名字
第13节:看C++大叔如何拥 java 妹子入怀……
第14节:坨——理解递归实现“汉诺塔”代码的关键
第15节:C++编译器如何实现 const(常量)?
第16节:C++如何为断言加上消息
第17节:初学C++到什么水平,算是合格的初级开发工程师?
第18节:C++编程要避免使用单例模式吗?
第19节:学习C++要学boost库吗?
第20节:C++的继承就是复制吗?
第21节:C++构造函数失败,如何中止创建对象?
第22节:C++学完多线程后,学什么呢?
第23节:string_view 适合用做函数的返回值类型吗?
第24节:为指针取别名,为何影响const属性?
第25节:std::enable_shared_from_this 的存在意义?
第26节:C++模板可变参数如何一次性解包?
第27节:Linux下的c++开发,平时是怎么调试代码的呢?
课文封面

instanceof 是 Java 语言的一个关键字,可用于判断某个对象的类型,是不是某个类 (其该类的派生类)。C++没有提供该关键字,但实现这一功能的实现并不困难。然而,本文大幅的回答重心在于说明:一个使用“面向对象”的好程序,应该尽量减少对“instance-of”此类功能的依赖。

一、instanceof 仅用于类系判断,并不能判断任意“obj Instance of class ”

如果我没记错的话, Java 中的 instanceof 是不能用在 没有关系的两个class身上的,比如:

// 伪代码:
interface B {};

class Db1 impl B {}; 
class Db2 impl B {};

class XX {};

那么:用于 判断 某个 B 对象是不是 Db1类型,或者是不是 Db2类型,可行;但用于 判断某个 Db1 对象是不是 Db2类型, 或者 某个 Db2 对象是不是 Db1 类型,就会得到编译错误;兄弟之间都不行,更不用说用于判断某个 B 对象是不是 XX 类型这样毫无关系的类型判断了。

几乎完全对应以上操作的C++代码,通常是 直接使用 dynamic_cast<类型T> (对象O) ,它返回值非空,就说明 “对象O” 是 “类型T”或者 “类型T”的派生类。

二、这点才重要:减少使用 instanceof(Java中) 或 dynamic_cast(C++中)

虽是举手之劳即可实现,但C++ 并没有为此功能提供 Java的“instanceof” 或者 C# 中 “is”, “as” 这类的简短表达,原因是C++认为“丑陋的事情,应该对应丑陋的代码”(也可以反过来理解:不要支持用户用漂亮的代码干丑陋的事)。而必须使用 instanceof 判断才能实现得了的设计,在所有推崇“面向对象” 的编程语言里,都是丑陋的。就以java为例,如:

www.stackoverflow.com 打不开了,否则去那边寻找这个问题,一找一大把。

以上对Java中使用instanceof的所有批评,都同样可用在C++中使用 dynamic_cast的情况。我们也给两个吧:

  • 恰当合理的设计以减免 dynamic_cast 的使用:
    Proper design to avoid the use of dynamic_cast?

  • dynamic_cast 稍好点,但仍然需要减免用它:[dynamic_cast is slightly better, but still should be avoided] (https://​www.sandordargo.com/blog/2023/04/26/without-rtti-your-code-will-be-cleaner)

简单说吧,在一门讲究OO的语言,写出需要基于类型硬判断的代码,真的又丑又臭。

那为什么语言要留着“instanceof”呢?通常就两个用处:一是特殊情况救急(之前或别人的代码实在太烂,完全重写来不及,特殊情况需要特殊手段);二是确实比较底层的库,各种反射都用上的情况下,用instanceof也就无所谓了。

你完全可以把像Java 如此纯粹面向对象的语言里的 instanceof 设施理解为家里某个角落里长期吃灰的那把专用于通马桶的皮揣子。家里得备着,但每次当你必须用它时,你的代码已经在漂屎。

三、如果非要一个 C++版的 instanceof ……

想要一个 C++ 版的 instanceof ,绝大多数情况可以这样:

template <typename ClassType, typename ObjectType> bool instanceof(ObjectType* obj) { return dynamic_cast<ClassType*>(obj); }

注意:obj设定为直接走指针。一定不要设计成走对象然后再加 try-catch……使用类型判断走业务逻辑已经是内心不安(见后),再使用异常以实现业务逻辑分流,是一个更要挨骂的错误用法。

实际使用:

struct A { virtual ~A(){} }; // 为什么要有 virtual 说明见后 struct B : A {}; int main() { B b; A* a = &b; std::cout << instanceof<A>(&b) << std::endl; // b 是一个A吗?是! std::cout << instanceof<B>(a) << std::endl; // a 是一个B吗?是! }

四、其实 Java 人有想过:怎么在Java中实现 dynamic_cast ,于是……

以上代码主要做了两件事:

  • (1) 把 dynamic_cast 取个外号叫:instanceof;
  • (2) 抛弃转换得到的新对象指针的完整信息,只留下这个指针是不是为空 这样一个布尔值。但这两点全是坏处。

事实上还有一件事,就是上面提到过的:强制入参 obj 为指针类型了。

先看 (1) :如果想到 instanceof 并不能用在两个不相关的类型身上这个事实,那么 instanceof 在名字显然不如 dynamic_cast 准确。我第一次看到 obj instance-of Class 时,我会以为 obj 和 Class 是自由的,比如可以用来判断 一个叫 “mouse” 的变量(对象),它的类型是到底是 “class 鼠标”,还是 “class 老鼠”。而“dynamic-cast”,由于都知道 C++ 是静态类型并且有大量相似操作但区分编译期实现(静态)和运行期实现(动态);最主要是有个 cast ,所以就没有脑洞大开,认为它可以干出动态 cast 类型上毫无相关的两个对象。

再看(2):转换后的 instanceof 用法示例:

if (instanceof<Triangle> (pShape)) { // pShape 是三角形吗? // 这里通常马上就要用到这个三角形 } else if (instanceof<Squares> (pShape)) { // pShape 是正方形吗? // 这里也通常马上就会用到这个正方形 }

如代码中所示,通常判断是不是某个明确类型之后,就会马上用到这个明确的类型。所以不如原来的 dynamic_cast<> 的返回结果:

if (auto pTriangle = dynamic_cast<Trianble *>(pShape)) // 可以使用 auto { pTrianble->doSomething(); }

第一点名字这种东西,叫定了后,是没法改了,但第二点的功能,Java也想要啊!事实上,我也忘了 Java 在哪个版本以后(应该在 2020年前后的事, Java14?),提供了这个功能的补丁,叫“Pattern Matching for instanceof / 用于 instanceof 的 模式匹配 ”, 于是代码可以写成:

// Java 的小补丁: Pattern Matching for instanceof if (shape instanceof Rectangle r) { // <- 注意变量 r return 2 * r.length() + 2 * r.width(); } else if (shape instanceof Circle c) { // <- 注意变量 c return 2 * c.radius() * Math.PI; } else { throw new IllegalArgumentException("Unrecognized shape"); }

退一万步, 如果确实用不到返回值中的更多信息,只需要一个 bool值,那在C++中,指针到bool类型的转换是内置且自动的,所以判断时用在起来,就和 boolean 变量一样方便:

if (dynamic_cast<Trianble *>(pShape)) // 不用比较 ... != nullptr { }

话说,看到这里,你真的还觉得C++有必要搞一个“instanceof”吗?

五、附带说一嘴C++中的“动态cast”和“静态cast”

以上测试的都是 ObjectType 和 ClassType 不同(但有继承关系)的情况,二者相同自然成立(即测试 A 类的对象 是不是 A 类),这里就不测试了。

不过,再往上处的回答,为什么要说“绝大情况”下,可以使用 dynamic_cast<T>(Obj) 实现 Obj 是不是 T 类型或 T 的某个派生类 的实例 (instance)呢? 那是因为 C++和Java还有个不同:C++存在非多态性质的“强行派生”,即基类一个虚函数都不存在——于是就完全不是“dynamic_cast”而是 “static_cast”了,后者无需运行时支持,性能更好。如果用了 dynamic_cast,编译器通常会指出“你用错了”。这种情况下,相当一写代码时就能知道 instanceof 的答案了,编译器一眼就能看出结果,何必费性能在运行期判断呢。

给个静态转换的例子:

struct A // 没有任何虚函数 { int a; void foo() {}; }; struct B : A { char c; void hello() {}; }; int main() { A* pa = new B; B* pb = static_cast<B*>(pa); // 不要判断,肯定成立,如果不成立,编译器会指出 pb->hello(); }