【问题标题】:Does it make sense to use const in an interface or not?在接口中使用 const 是否有意义?
【发布时间】:2011-05-31 16:49:40
【问题描述】:

我有一个模块执行一些计算,并在计算期间与其他模块通信。由于计算模块不想依赖其他模块,所以它暴露了一个这样的接口(当然这是一个非常简化的版本):

class ICalculationManager
   {
   public:
      double getValue (size_t index) = 0;
      void setValue (size_t index, double value) = 0;
      void notify (const char *message) = 0;
   };

想要使用计算模块的应用程序需要编写自己的接口实现,并将其提供给计算工具,如下所示:

MyCalculationManager calcMgr;
CalculationTool calcTool (calcMgr);
calcTool.calculate();

我现在想知道在 ICalculationManager 接口的方法中添加“const”是否有意义。

getValue 方法只得到一些东西而不改变任何东西似乎是合乎逻辑的,所以我可以使这个 const。并且 setValue 可能会更改数据,因此不会是 const。 但是对于像 notify 这样更通用的方法我不能确定。

事实上,对于任何一种方法,我现在都无法确定该方法是否真正实现为 const 方法,如果我将接口方法设为 const,我将强制所有实现也为 const,即可能不想要。

在我看来,只有当你事先知道你的实现是什么以及它是否是 const 时,const 方法才有意义。这是真的吗?

把这种接口的方法做成const不是有意义吗?如果有意义,有什么好的规则来确定方法是否应该是 const ,即使我不知道实现是什么?

编辑:将参数从“char *”更改为“const char *”,因为这会导致不相关的答案。

【问题讨论】:

  • +1 引起了很多讨论:D

标签: c++ interface constants


【解决方案1】:

当您向客户宣传调用该函数永远不会改变对象的外部可见状态时,您创建了一个函数const。您的对象只有一个可以检索的状态,getValue。

因此,如果 getValue 可以导致下一个 getValue 返回不同的值,那么可以确定,将其保留为非 const。如果你想告诉客户调用 getValue() 永远不会改变下一个 getValue() 返回的值,那就让它成为 const。

通知也一样:

double d1 = mgr->getValue(i);
mgr->notify("SNTH");  // I'm cheating.
double d2 = mgr->getValue(i);
assert(d1==d2);

如果这对所有情况都适用并且所有 i 都适用,那么 notify() 应该是 const。否则不应该。

【讨论】:

  • 您假设接口的编写者也是实现的编写者。在这种情况下,定义接口(或契约)的是接口的调用者,由于它不知道将如何实现,所以它不知道它是否应该是 const 。
  • 没有。我不是。但我放弃了。接受投票最多的答案,因为它会给你带来所有好处。 -> “在这种情况下,定义接口(或合约)的是接口的调用者,...”
  • @Patrick:我认为 Noah 的评论与特定的实现者无关——它与接口的 调用者 的期望有关。 (特别是答案的第二段)接口中这些方法的行为/交互是接口的一部分——接口不仅仅是使代码在语法上有效的原因——它有很多处理使代码在语义上有效的原因。如果您无法确切地想到接口实现者应该做什么,即一种方法如何影响接口中的另一种方法,那么接口是错误的。 @诺亚:+1。
  • 接口在概念上不仅决定了技术细节(参数/返回值),还决定了方法的含义。接口可以说“调用 getTime() 必须返回 UTC 时间”。如果一个实现返回本地时间,那么它的实现是错误的。同样,如果接口显示“getValue() 无法更改对象的外部可见状态”,则将其设为 const。如果接口说“实现可以改变它”,那么让它成为非常量。简而言之:接口决定,实现必须遵循它。并且接口应该是明确的。
  • 请注意,@tenfour 的注释中没有提到代码中存在的任何内容;) -- 这是接口的语义,而不是句法。
【解决方案2】:

是的。无论何时何地都应该使用const 是明智的。执行计算的方法(这是您的界面所建议的)应该改变它的可观察行为是没有意义的,因为它调用了“通知”。 (就此而言,通知与计算有什么关系?)

我创建了一个界面成员const,你不会强迫客户成为const——你只是允许他们使用const ICalculationManager

我可能会制作Notify const。如果客户端由于通知需要做某事non-const,那么Notify 不是一个好的方法名称——该名称暗示了非状态修改转换,例如日志记录,而不是修改。

例如,大多数时候你传递你的接口,你会想要使用 pass-by-reference-to-const 来传递接口实现器,但如果方法不是const,你不能那样做。

【讨论】:

  • 这个答案都搞砸了。使函数“const”与客户需要做的任何事情无关。如果客户需要做一些非常量的事情,那么它可以;与 notify() 是否为 const 无关。你的最后一点是半真半假的;您可能在大约 1/2 的时间内通过 const-reference 传递对 base 的引用。不,不要让成员 const 尽可能;当它们应该成为常量时使它们成为常量。
  • 如果实现决定缓冲所有通过通知提供给它的消息,那么它将在每次调用通知时更改其内部缓冲区。因此它不能是常量。不过,通知对我来说似乎是个好名字。
  • @Patrick:然后接口可以调用其他一些非常量类来做到这一点。我对“通知”这个名称的问题是它完全没有告诉你该方法应该做什么。是通用日志吗?是否调用它让接口实现者知道即将发生计算?它甚至与执行计算的功能有关吗?我什至不会将这样的方法放在与计算相同的界面中,因为计算和日志记录没有任何关系......
  • @Noah:根据您的评论稍作修改,但我仍然认为我的答案的最后一部分是正确的。必须修改方法的参数表明该方法应该被重构为一个对象(即“提取方法对象”),而不是通过参数返回数据。
  • 接口决定了 OBSERVABLE 行为,所以应该像 Billy ONeal 所说的那样使用 constness - 明智地,就像在任何其他类中一样。如果内部内容需要更改,请使用mutable。接口定义的可观察行为不应被实现破坏。
【解决方案3】:

接口应该指导实现,而不是相反。如果您还没有决定一个方法或参数是否可以是 const,那么您还没有完成设计。

使用 const 是一种断言代码可以做什么或不可以做什么的方式。这在推理一段代码时非常有价值。例如,如果您的notify 参数不是 const,那么它会对消息做出哪些更改?如果需要,它将如何使消息变大?

编辑:您似乎知道声明 const 参数的价值,所以让我们以此为基础。假设您想要一个函数来记录计算的值:

void RecordCalculation(const ICalculationManager *calculation);

您可以在该指针上调用的唯一方法是 const 方法。可以肯定的是,函数返回后,对象将保持不变。这就是我对代码进行推理的意思——您可以绝对确定对象不会被更改,因为如果您尝试,编译器会生成错误。

编辑 2: 如果您的对象包含一些内部状态,这些状态将被修改以响应逻辑上的 const 操作,例如缓存或缓冲区,请继续使用 mutable 关键字 on那些成员。这就是它的发明目的。

【讨论】:

  • 确实notify的参数应该是const,我同意。但这并不能回答与方法是否为 const 相关的问题。我的 CalculationTool 无法知道 ICalculationManager 的实现将在通知中做什么。实现可以只将消息发送到 std::cout (而不是它可以是 const ),或者它可以缓冲它(那么它不能是 const )。如果我不知道实现会做什么,我怎么知道方法是否应该是 const ?
  • @Patrick - 不,接口的客户端不能也不应该知道该接口背后的实现在做什么。但是请看我的回答。客户端可以并且必须能够依赖于被遵守并以某种方式行事的实现。您对“const”的使用告诉这些客户他们可以确定和不能确定的内容。
【解决方案4】:

对我来说,这仅取决于您的界面合同。

对于 getter 方法,我不明白为什么它应该更改任何数据,如果发生这种情况,也许 mutable 是一种选择。

我同意 setter 方法,而不是 const ,因为这肯定会以某种方式改变数据。

如果不知道它对您的系统意味着什么,就很难说通知。另外,您是否希望实现修改消息参数?如果是现在,它也应该是 const。

【讨论】:

    【解决方案5】:

    无需阅读您的整篇文章:当然,如果您想在 const 上下文中使用一个对象(继承 ICalculationManager),这是有道理的。通常,如果您不操作私有数据,则应始终使用 const 限定符。

    编辑: 就像 Mark Ransom 说的:你需要确切地知道你的界面函数应该如何表现,否则你就没有完成设计。

    【讨论】:

    • 我不同意。接口只定义了合约;而不是应该如何实施。由于我不知道接口的实现将如何实现 notify 方法(将其发送到 std::cout,或将其缓冲在列表中),因此我不知道该方法是否应该是 const。如果我将其设为 const,那么我最终可能需要使用可变数据成员或大量 const_cast 的所有实现,这完全违背了 const 关键字的目的。
    • @Patrick - 你部分在那里。再次,请参阅我的答案。正确使用可变数据成员不会破坏 const 的用途。
    • 修改私人数据不应成为使用const时的决定因素。
    • @tenfour - 从“私有”数据说起,我的意思是每个包含类的数据。因此,它是使用 const 时的决定因素。
    • 只应考虑外部可见状态。私有数据是特定于实现的,与接口的设计无关。如果在实现中,私有数据必须在 const 方法中更改,请使用 mutable 来适应。
    【解决方案6】:

    我知道我会为此受到很多反对,但在我看来,C++ 中 const 正确性的用处被大大夸大了。 const 想法是原始的(它只捕获一点概念......更改/不更改)并且成本高昂,甚至包括代码复制的必要性。它也不能很好地扩展(考虑 const_iterators)。

    更重要的是,我什至不记得有一个案例(甚至一个案例),其中 const 正确性机制帮助我发现了一个真正的逻辑错误,即我试图做一些我不应该做的事情。相反,每次编译器阻止我时,const 声明部分都会出现问题(即我试图做的事情在逻辑上是合法的,但是方法或参数在关于 const 的声明中存在问题)。 在所有情况下,我都记得我在哪里遇到了与 const 正确性相关的编译器错误,修复只是添加了一些缺少的 const 关键字或删除了一些多余的关键字……如果不使用 const 正确性的想法,这些错误就不会出现根本就在那里。

    我喜欢 C++,但我当然不会爱死它的每一点(题外话:当我采访某人时,我经常问的一个问题是“你不喜欢 的哪些部分?” ...如果答案是“无”,那么仅表示我正在与之交谈的人仍处于狂热阶段,显然没有真正的经验)。

    C++ 的许多部分都非常好,有些部分在 IMO 中很糟糕(例如流格式),有些部分并不可怕,但逻辑上既不美观也不实用。 const-correctness 的想法是 IMO 在这个灰色区域(这不是一个新手印象......经过许多行和多年的 C++ 编码后,我得出了这个结论)。 可能是我,但显然 const 正确性解决了我的大脑没有的问题......我还有很多其他问题,但不是我应该何时更改实例状态以及何时不应该更改的困惑。

    不幸的是(与流格式不同)你不能忽略 C++ 中的 const 正确性机制,因为它是核心语言的一部分,所以即使我不喜欢它,我也不得不遵守它。

    您现在可能会说...好吧,但问题的答案是什么?只是我不会对语义描述的那部分感到太疯狂……它只是一点点,而且价格很高;如果您不确定并且可以在不声明 constness 的情况下逃脱,请不要这样做。引用或方法的常量对编译器没有帮助(请记住,它可以合法地丢弃),它已被添加到 C++ 中,只是作为对程序员的帮助。然而,我的经验告诉我(考虑到高成本和低回报)这根本不是真正的帮助。

    【讨论】:

    • -1 表示 FUD,以及不回答 OP 提出的问题。
    • "..我什至不记得有一个案例(甚至一个案例),其中 const 正确性机制通过发现一个真正的逻辑错误来帮助我,那就是我试图做我不应该做的事情t do...” 我们的系统中存在内存泄漏,原因是有人编写了代码来调用 std::auto_ptr 变量(在几个地方)上的“release()”而不是“get()”。发生这种情况是因为开发人员没有遵守我们应该尽可能使 auto_ptr const 的编码准则。如果它被声明为 const,我们会得到一个编译错误。
    • 当编译器为您提供了一个可以在编译时而不是运行时强制执行概念的工具时,请使用它。
    • 这么多宗教狂热!我同意海报。 const 正确性被高估了,不应该被添加到语言中。许多语言没有它。例如,Ocaml 具有“可变”,这完全不同。我的语言 Felix,它模拟 C++,不提供对 const 的任何支持。
    • "我什至不记得有一个案例(甚至一个案例),其中 const 正确性机制通过发现一个真正的逻辑错误来帮助我,那就是我试图做我不应该做的事情。” 因为 const-correctness 检查?您说编译器多次阻止您,因为您在声明 const/non-const 时犯了错误。如果它不会阻止你怎么办......
    猜你喜欢
    • 2020-01-16
    • 1970-01-01
    • 2015-12-23
    • 2020-02-07
    • 2011-04-02
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多