怀疑应该是 学 java 的同学,总有人问 C++ 中为什么没有 instanceof 。事实上,学 java 的人,更更应该知道, 在 java 如此面向对象的语言中,日常业务逻辑的实现,但凡到了不得不用 instanceof 的时候,99% 以上就是设计或实现错误了。
如果我没记错的话, 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”的派生类。
虽是举手之劳即可实现,但C++ 并没有为此功能提供 Java的“instanceof” 或者 C# 中 "is", "as" 这类的简短表达,原因是C++认为“丑陋的事情,应该对应丑陋的代码”(也可以反过来理解:不要支持用户用漂亮的代码干丑陋的事)。而必须使用 instanceof 判断才能实现得了的设计,在所有推崇“面向对象” 的编程语言里,都是丑陋的。就以java为例,如:
Java “instanceOf”: Why And How To Avoid It In Codearmedia.com/blog/instanceof-avoid-in-code/正在上传…重新上传取消
The Instanceof Code Smell and One Way to Get Around It - DZonedzone.com/articles/instanceof-considered-harmful正在上传…重新上传取消
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的情况。我们也给两个吧:
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 is slightly better, but still should be avoidedwww.sandordargo.com/blog/2023/04/26/without-rtti-your-code-will-be-cleaner
简单说吧,在一门讲究OO的语言,写出需要基于类型硬判断的代码,真的又丑又臭。
那为什么语言要留着“instanceof”呢?通常就两个用处:一是特殊情况救急(之前或别人的代码实在太烂,完全重写来不及,特殊情况需要特殊手段);二是确实比较底层的库,各种反射都用上的情况下,用instanceof也就无所谓了。
你完全可以把像Java 如此纯粹面向对象的语言里的 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吗?是!
}
以上代码主要做了两件事:(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”吗?
以上测试的都是 ObjectType 和 ClassType 不同(但有继承关系)的情况,二者相同自然成立(即测试 A 类的对象 是不是 A 类),这里就不测试了。
不过,再往上处的回答,为什么要说“绝大情况”下,可以使用 dynamic_cast
给个静态转换的例子:
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();
}