我们的 C++ 教授提到,使用 operator-> 的结果作为另一个 operator-> 的输入被认为是不好的风格。
所以不要写:
return edge->terminal->outgoing_edges[0];
他更喜欢:
Node* terminal = edge->terminal;
return terminal->outgoing_edges[0];
- 为什么这被认为是不好的风格?
- 我怎样才能重组我的程序以避免“坏风格”,同时也避免根据上述建议创建的额外代码行?
我们的 C++ 教授提到,使用 operator-> 的结果作为另一个 operator-> 的输入被认为是不好的风格。
所以不要写:
return edge->terminal->outgoing_edges[0];
他更喜欢:
Node* terminal = edge->terminal;
return terminal->outgoing_edges[0];
有很多原因。
得墨忒耳定律给出了一个结构性原因(请注意,您的 C++ 教授代码仍然违反了这一点!)。在您的示例中,edge必须了解terminaland outgoing_edges。这使得它紧密耦合。
作为备选
class Edge {
private:
Node* terminal;
public:
Edges* outgoing_edges() {
return terminal->outgoing_edges;
}
}
现在,您可以outgoing_edges在一处更改实现,而无需到处更改。老实说,对于像图这样的数据结构(它是紧密耦合的,边和节点不能相互逃避),我真的不买这个。这在我的书中过于抽象了。
还有空引用问题,在表达式a->b->c中,如果b为空怎么办?
你应该问你的教授为什么他认为这是不好的风格。我不。然而,我认为他在终端声明中省略了 const 是不好的风格。
对于这样的一个片段,它可能不是坏的风格。但是考虑一下:
void somefunc(Edge *edge)
{
if (edge->terminal->outgoing_edges.size() > 5)
{
edge->terminal->outgoing_edges.rezize(10);
edge->terminal->counter = 5;
}
++edge->terminal->another_value;
}
这开始变得笨拙 - 难以阅读,难以书写(我在打字时犯了大约 10 个错别字)。它需要对这两个类的 operator-> 进行大量评估。如果操作员是微不足道的,那么可以,但如果操作员做了任何令人兴奋的事情,它最终会做很多工作。
所以有2个可能的答案:
像这样的一个片段,你无法避免额外的行。在类似上面的情况下,它会减少打字。
嗯,我不确定你的教授是什么意思,但我有一些想法。
首先,这样的代码可能“不那么明显”:
someObjectPointer->foo()->bar()->foobar()
您无法真正说出 的结果是什么foo(),因此您无法真正说出bar()被调用的内容。那是同一个对象吗?或者它可能是一个正在创建的临时对象?或者也许是别的东西?
另一件事是如果你必须重复你的代码。考虑一个这样的例子:
complexObject.somePart.someSubObject.sections[i].doSomething();
complexObject.somePart.someSubObject.sections[i].doSomethingElse();
complexObject.somePart.someSubObject.sections[i].doSomethingAgain();
complexObject.somePart.someSubObject.sections[i].andAgain();
将其与以下内容进行比较:
section * sectionPtr = complexObject.somePart.someSubObject.sections[i];
sectionPtr->doSomething();
sectionPtr->doSomethingElse();
sectionPtr->doSomethingAgain();
sectionPtr->andAgain();
您可以看到第二个示例不仅更短,而且更易于阅读。
有时函数链更容易,因为您不需要关心它们返回的内容:
resultClass res = foo()->bar()->foobar()
对比
classA * a = foo();
classB * b = a->bar();
classC * c = b->foobar();
另一件事是调试。调试长链函数调用非常困难,因为您根本无法理解它们中的哪一个导致了错误。在这种情况下,打破链条有很大帮助,更不用说你也可以做一些额外的检查,比如
classA * a = foo();
classB * b;
classC * c;
if(a)
b = a->bar();
if(b)
c = b->foobar();
所以,总而言之,你不能说它是一种“坏”或“好”的风格。你必须首先考虑你的情况。
您显示的两个代码片段(当然)在语义上是相同的。当谈到风格问题时,建议你简单地遵循教授的要求。
比你的第二个代码片段稍微好一点的是:
Node* terminal = (edge ? edge->terminal : NULL);
return (terminal ? terminal->outgoing_edges[0] : NULL);
我假设传出边缘是一个数组;如果不是,您还必须检查它是否为 NULL 或空。
目前的形式都不一定更好。但是,如果您要在第二个示例中添加对 null 的检查,那么这将是有意义的。
if (edge != NULL)
{
Node* terminal = edge->terminal;
if (terminal != NULL) return terminal->outgoing_edges[0];
}
尽管即使这样仍然很糟糕,因为谁会说这outgoing_edges是正确填充或分配的。一个更好的选择是调用一个函数outgoingEdges(),它会为您进行所有很好的错误检查,并且永远不会导致您出现未定义的行为。