【问题标题】:Mark a member function as const when it is conceptually not将成员函数标记为 const 在概念上不是
【发布时间】:2018-02-07 15:03:00
【问题描述】:

据我在这里和那里读到的,应该尽可能使用const。然而,我有一个案件一直困扰着我。

如果成员函数不改变任何成员变量值但在概念上不是const 函数,我是否应该将其标记为const

例如:

class Engine{
public:
    int status;
};

class Car{
public:
   void start() const{
        engine_->status = 1;
    }
private:
    std::unique_ptr<Engine> engine_;
};

编译器将接受start()常量,因为engine_ 作为指针没有改变。但是,至少在 IMO 看来,在一个名为 Car 的类中名为 start 的函数是一个 const ,这似乎太不现实了!

这个例子只是一个简单的例子。通常,Car 类的一些内部状态应该相应地更新,使const 关键字不可行。不过,这个小例子只是为了说明我的想法。

【问题讨论】:

  • STL 使用调用const 成员函数是线程安全的约定。你的start 不是。如果你应该遵循这个约定是有争议的。
  • 我记得读过 Scott Myers 讨论他认为的逻辑 const-ness 和物理 const-ness 之间的区别。那是关于引入mutable 成员(例如互斥锁)。他的观点是关于证明可修改成员的合理性,因为对象的逻辑状态保持不变,所以成员函数是合理的 const。我认为类似的推理在这里也适用。逻辑状态发生变化,因此省略 const 可能是合理的。
  • 调用者通常期望const 指的是对象的逻辑状态,而不是它的表示。所以最好不要将这种情况标记为 const。相反,不表达逻辑状态的成员变量(例如缓存机制)通常声明为mutable,以允许函数为const,即使确实改变了对象的表示。
  • 这里可能值得一提的是最小惊讶原则——在这种情况下,如果你的类的用户会惊讶地发现 start() 方法被标记为 const,那么这很好不标记方法 const 的参数。 en.wikipedia.org/wiki/Principle_of_least_astonishment

标签: c++ c++11 constants


【解决方案1】:

判断函数是否应为const 的一个简单指标是:

Type a{...};
Type b{...};

bool comp1 = a == b;

b.some_func(...);

bool comp2 = a == b;

如果comp1comp2 可以永远不同,那么some_func 不是const

显然,并非每种类型都有operator== 重载,但大多数类型至少有一个概念性的想法,即您将测试什么以查看它们是否相等。具有不同engine 状态的不同Car 实例将是不平等的。因此,改变engine 状态的函数不是const

【讨论】:

  • 假设Typeint&amp;。然后您的代码(用对底层int 的变异操作替换some_func)可能会失败。引用语义类型(视图)可以是const,即使操作修改了底层的被引用对象,== 可能会比较底层对象而不是引用所指的身份。或者,简而言之std::reference_wrapper&lt;T&gt;
  • @Yakk 标记为conststd::reference_wrapper&lt;T&gt; 的哪个成员修改了底层的引用对象?
  • @xskxzr get() 返回 T&amp; 并且是 const。与operator T&amp;() 相同
【解决方案2】:

在您的情况下,编译器允许您制作 start() const,因为 constness 通过指针的不完美传播。如果你用Engine 类型的对象替换你的指针,你的问题就会消失。所以答案是否定的,在这种情况下不应该是const,因为使用Engine 作为智能指针或实例是内部细节,不应影响Car 类的公共接口。

就我在这里和那里的阅读而言,应尽可能使用 const。

这种说法太笼统了,与任何通用建议一样,不应在每种情况下都正式使用。

【讨论】:

    【解决方案3】:

    在您的示例中,您可能需要std::experimental::propagate_const:

    class Car{
    public:
       void start() { engine_->status = 1; }
    private:
        std::experimental::propagate_const<std::unique_ptr<Engine>> engine_;
    };
    

    那么你的start 不能再是const

    【讨论】:

    • 虽然不是我要问的问题的核心,但真的很感谢你提到它!我一直想知道为什么没有这样的东西。
    • 一旦const 被正确传播,我很难看到const 方法在概念上不是这样的情况。使用全局变量(任何类型的静态成员或外部文件),但我会说您对常量性的关注将被该设计(问题)所超越。
    【解决方案4】:

    const 的含义可能会有所不同。

    • 如果保留==,则为const

    • 如果您的类型遵循引用语义并且它不会改变所引用的内容,则为 const

    • 如果可以以合理的方式将其用于任何右值或左值,那么它就是 const

    • 如果可以安全地从多个线程中使用,则为 const

    • 如果编译为const,则为const

    • 如果对象声称是内部的任何状态都没有被它改变,则某事是const

    所有这些都是判断方法或参数是否为const的合理规则。

    要特别小心的一点是要知道T const*T*const 之间的区别,并且不要意外地将顶级const 用作内部const。不是const iterator,而是const_iterator。不是const gsl::span&lt;int&gt;,而是gsl::span&lt;const int&gt;。不是const unique_ptr&lt;T&gt;,是unique_ptr&lt;T const&gt;

    另一方面,vector 是一个值语义类型;它假装它的缓冲区是它的一部分(即使这是一个谎言)。不是vector&lt;const T&gt;,是const vector&lt;T&gt;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-15
      • 2012-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-10
      • 1970-01-01
      相关资源
      最近更新 更多