我想需要回答的问题是,为什么有善意的人一开始就会写支票。
最常见的情况可能是您有一个类,它是自然递归调用的一部分。
如果您有:
struct Node
{
Node* left;
Node* right;
};
在C语言中,你可能会这样写:
void traverse_in_order(Node* n) {
if(!n) return;
traverse_in_order(n->left);
process(n);
traverse_in_order(n->right);
}
在C++中,将此函数作为成员函数是很好的选择:
void Node::traverse_in_order() {
// <
left->traverse_in_order();
process();
right->traverse_in_order();
}
在 C++ 标准化之前的早期,强调成员函数是隐式参数为
this
的函数的语法糖。代码会先用C++编写,然后转换为等效的C代码并进行编译。甚至有明确的示例表明将
this
与 null 进行比较是有意义的,最初的 Cfront 编译器也利用了这一点。因此,对于检查来说,从 C 背景出发,显而易见的选择是:
if(this == nullptr) return;
注意:Bjarne Stroustrup甚至提到了多年来this
的规则已经发生了变化,可以在此处找到相关信息。
这个问题在许多编译器上工作了很多年。当标准化发生时,这种情况就改变了。最近,编译器开始利用调用成员函数的优势,其中this
为空指针是未定义行为,这意味着这个条件总是false
,编译器可以省略它。
这意味着要遍历这个树,你需要做以下两件事之一:
void Node::traverse_in_order() {
if(left) left->traverse_in_order();
process();
if(right) right->traverse_in_order();
}
这也意味着需要在每个调用点检查是否存在空根节点。
不要使用成员函数。
这意味着您正在编写旧的C风格代码(可能是静态方法),并在显式将对象作为参数传递的情况下调用它。例如,您回到了在调用点编写 Node::traverse_in_order(node);
而不是 node->traverse_in_order();
的方式。
我认为在符合标准的情况下修复此特定示例最简单/最整洁的方法实际上是使用哨兵节点而不是nullptr
。
Node sentinel;
void Node::traverse_in_order() {
if(this == &sentinel) return;
...
}
前两个选项都不太吸引人,虽然代码可以通过,但他们写了糟糕的代码,使用了this == nullptr
而不是使用适当的修复方法。
我猜这就是一些代码库演化到其中包含 this == nullptr
检查的原因。
this
作为隐式参数的事实,因此他们开始像使用显式参数一样使用它。但实际上并不是这样。当你对一个空指针的this
进行解引用时,你将会像对其他任何空指针一样触发未定义行为。就是这样简单。如果想要传递nullptr,请使用显式参数,傻瓜。它不会更慢,不会更笨重,并且具有此类API的代码深入内部,因此范围非常有限。故事结束。 - Kuba hasn't forgotten Monica