怎样在c++中实现instanceof?

怀疑应该是 学 java 的同学,总有人问 C++ 中为什么没有 instanceof 。事实上,学 java 的人,更更应该知道, 在 java 如此面向对象的语言中,日常业务逻辑的实现,但凡到了不得不用 instanceof 的时候,99% 以上就是设计或实现错误了。

一、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为例,如:

  • Java的“instanceof”:为什么要避免以及如何避免在代码中用到它:

Java “instanceOf”: Why And How To Avoid It In Code​armedia.com/blog/instanceof-avoid-in-code/正在上传…重新上传取消

  • 来自“instanceof”的代码异味以及解决方法之一:

The Instanceof Code Smell and One Way to Get Around It - DZone​dzone.com/articles/instanceof-considered-harmful正在上传…重新上传取消

  • 为什么在Java中使用instanceof是糟糕的?

Why is it bad to use instanceof in Java?​itexpertly.com/why-is-it-bad-to-use-instanceof-in-java/

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

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

  • 恰当合理的设计以减免 dynamic_cast 的使用:

Proper design to avoid the use of dynamic_cast?​softwareengineering.stackexchange.com/questions/363410/proper-design-to-avoid-the-use-of-dynamic-cast

  • dynamic_cast 稍好点,但仍然需要减免用它:

dynamic_cast is slightly better, but still should be avoided​www.sandordargo.com/blog/2023/04/26/without-rtti-your-code-will-be-cleaner

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

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

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

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

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

template 
bool instanceof(ObjectType* obj) 
{
    return dynamic_cast(obj);
}

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

实际使用:

struct A { virtual ~A(){} }; // 为什么要有 virtual 说明见后
struct B : A {};

int main()
{
    B b;
    A* a = &b;
    std::cout << instanceof(&b) << std::endl; // b 是一个A吗?是!
    std::cout << instanceof(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 (pShape)) {  // pShape 是三角形吗?
    // 这里通常马上就要用到这个三角形
} else if (instanceof (pShape)) { // pShape 是正方形吗?
    // 这里也通常马上就会用到这个正方形
}

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

if (auto pTriangle = dynamic_cast(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(pShape)) // 不用比较 ... != nullptr
{
}
话说,看到这里,你真的还觉得C++有必要搞一个“instanceof”吗?

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

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

不过,再往上处的回答,为什么要说“绝大情况”下,可以使用 dynamic_cast(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(pa); // 不要判断,肯定成立,如果不成立,编译器会指出
    pb->hello();    
}

你可能感兴趣的:(白话C++,c++,开发语言,java)