16

我们的 C++ 教授提到,使用 operator-> 的结果作为另一个 operator-> 的输入被认为是不好的风格。

所以不要写:

return edge->terminal->outgoing_edges[0];

他更喜欢:

Node* terminal = edge->terminal;
return terminal->outgoing_edges[0];
  1. 为什么这被认为是不好的风格?
  2. 我怎样才能重组我的程序以避免“坏风格”,同时也避免根据上述建议创建的额外代码行?
4

5 回答 5

19

有很多原因。

墨忒耳定律给出了一个结构性原因(请注意,您的 C++ 教授代码仍然违反了这一点!)。在您的示例中,edge必须了解terminaland outgoing_edges。这使得它紧密耦合。

作为备选

class Edge {
 private:
   Node* terminal;
 public:
   Edges* outgoing_edges() {
      return terminal->outgoing_edges;
   }
}

现在,您可以outgoing_edges在一处更改实现,而无需到处更改。老实说,对于像图这样的数据结构(它是紧密耦合的,边和节点不能相互逃避),我真的不买这个。这在我的书中过于抽象了。

还有空引用问题,在表达式a->b->c中,如果b为空怎么办?

于 2012-07-10T13:11:43.157 回答
12

你应该问你的教授为什么他认为这是不好的风格。我不。然而,我认为他在终端声明中省略了 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个可能的答案:

  1. 可维护性
  2. 效率

像这样的一个片段,你无法避免额外的行。在类似上面的情况下,它会减少打字。

于 2012-07-10T13:15:08.207 回答
6

嗯,我不确定你的教授是什么意思,但我有一些想法。

首先,这样的代码可能“不那么明显”:

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();

所以,总而言之,你不能说它是一种“坏”或“好”的风格。你必须首先考虑你的情况。

于 2012-07-10T13:17:16.450 回答
3

您显示的两个代码片段(当然)在语义上是相同的。当谈到风格问题时,建议你简单地遵循教授的要求。

比你的第二个代码片段稍微好一点的是:

Node* terminal = (edge ? edge->terminal : NULL);
return (terminal ? terminal->outgoing_edges[0] : NULL);

我假设传出边缘是一个数组;如果不是,您还必须检查它是否为 NULL 或空。

于 2012-07-10T13:16:13.177 回答
1

目前的形式都不一定更好。但是,如果您要在第二个示例中添加对 null 的检查,那么这将是有意义的。

if (edge != NULL)
{
   Node* terminal = edge->terminal;
   if (terminal != NULL) return terminal->outgoing_edges[0];
}

尽管即使这样仍然很糟糕,因为谁会说这outgoing_edges是正确填充或分配的。一个更好的选择是调用一个函数outgoingEdges(),它会为您进行所有很好的错误检查,并且永远不会导致您出现未定义的行为

于 2012-07-10T13:41:52.210 回答