我应该声明这些方法是什么?

时间:2010-10-19 12:42:57

标签: c++ const static-code-analysis const-method

我正在研究一些C ++代码,其中我有几个带有私有方法的管理器对象,例如

void NotifyFooUpdated();

在此对象的侦听器上调用OnFooUpdated()方法。

请注意,它们不会修改此对象的状态,因此它们在技术上可以成为const方法,即使它们通常会修改整个系统的状态。特别是,侦听器对象可能会回调此对象并对其进行修改。

就我个人而言,我希望保留它们,而不是声明它们const

但是,我们的静态代码检查器QAC将此标记为偏差,因此我要么必须声明它们const,要么我必须争论为什么它们应该保持非常量并获得偏差的补助。 / p>

没有声明这些方法const的论据是什么? 或者我应该关注QAC并声明它们const
我应该采用仅限于此对象的严格本地观点,还是将系统视为一个整体?

13 个答案:

答案 0 :(得分:3)

如果侦听器存储为指针集合,即使对象是const,也可以对它们调用非const方法。

如果合同是侦听器在收到通知时可能更新其状态,则该方法应为非const。

你是说听众可以回调对象并修改它。但是监听器不会自行改变 - 因此Notify调用可以是const,但是你将一个非const指针传递给你自己的对象。

如果侦听器已经有了该指针(它只侦听一个东西),那么你可以将两个方法都设为const,因为你的对象被修改是一个副作用。发生的事情是:

A来电B B修改A作为结果。

因此,呼叫B间接导致其自身的修改,但不是对自我的直接修改。

如果是这种情况,你的方法可能也可能应该是const。

答案 1 :(得分:3)

松散地说,你有一个容器类:一个充满观察员的经理。在C和C ++中,您可以使用非const值的const容器。考虑一下你是否删除了一层包装:

list<Observer> someManager;

void NotifyFooUpdated(const list<Observer>& manager) { ... }

对于采用const列表的全局NotifyFooUpdated,您会发现没什么奇怪的,因为它不会修改列表。该const参数实际上使得参数解析更加宽松:该函数接受const和非const列表。类方法版本的所有const注释都是const *this

解决另一个问题:

  

如果你不能保证调用函数的对象在函数调用之前和之后保持不变,你通常应该把它保留为非const。

如果调用者只有对象的引用,那是唯一合理的。如果对象是全局的(就像在原始问题中那样)或在线程环境中,则任何给定调用的常量都不能保证对象的状态在调用期间保持不变。没有副作用且始终为相同输入返回相同值的函数是 pure 。 NotifyFooUpdate()显然不是纯粹的。

答案 2 :(得分:2)

  

没有声明这些方法const的论据是什么?   或者我应该关注QAC并声明它们const
  我应该采用仅限于此对象的严格本地观点,还是将系统视为一个整体?

你知道的是那个管理员对象需要做而不是更改。然后,管理器在上调用函数的对象可能会更改,或者它们可能不会。你不知道。

根据您的描述,我可以设想这样一种设计,其中所有涉及的对象都是const(并且可以通过将它们写入控制台来处理通知)。如果您不执行此功能const,则禁止此操作。如果你成功const,你允许两者。

我想这是支持制作const的论据。

答案 3 :(得分:1)

如果无法保证调用函数的对象在函数调用之前和之后保持不变,则通常应将其保留为非const。想一想 - 你可以编写一个监听器,在对象是非const时插入它,然后使用这个函数来违反const的正确性,因为你曾经有过访问过去那个非const的对象。那是错的。

答案 4 :(得分:1)

我认为它们应该保持非常量。这是基于我的看法,即管理者对象的状态,实际上是它管理的所有对象的状态的总和加上任何内在状态,即State(Manager) = State(Listener0) + State(Listener1) + ... + State(ListenerN) + IntrinsicState(Manager)

虽然源代码中的封装可能与这种运行时关系相悖。根据您的描述,我相信这个聚合状态反映了程序的运行时行为。

强化我的论点:我断言代码应该努力反映程序的运行时行为,而不是严格遵守编译的确切语义。

答案 5 :(得分:1)

const ,或不 const :这就是问题。

const的参数:

  • 有问题的方法不会修改对象的状态。
  • 你的静态代码检查器将缺少const标记为偏差,也许你应该听它。

反对const

的论点
  • 这些方法整体修改了系统的状态。
  • 监听器对象我修改对象。

就我个人而言,我将它留作const,事实上它可能会修改整个系统的状态,就像一个空指针引用。它是一个const方法,它不会修改有问题的对象,但它会使程序崩溃,从而修改整个系统的状态。

答案 6 :(得分:1)

对于 const 有一些很好的论据,所以这里是我的看法: -

就个人而言,我不会将这些“OnXXXUpdated”作为我的经理课程的一部分。我认为这就是为什么对最佳实践存在一些困惑。您正在向相关方通知某些事情,并且不知道在通知过程中对象的状态是否会发生变化。它可能会,也可能不会。 对我来说显而易见的是,通知感兴趣方的过程应为const。

所以,要解决这个困境,我会这样做:

从经理类中删除OnXXXXUpdated函数。

编写一个通知管理器,这是一个原型,具有以下假设:

“Args”是在发生通知时传递信息的任意基类

“委托”是某种函数指针(例如FastDelegate)。

class Args
{
};

class NotificationManager
{
private:
    class NotifyEntry
    {
    private:
        std::list<Delegate> m_Delegates;

    public:
        NotifyEntry(){};
        void raise(const Args& _args) const
        {
            for(std::list<Delegate>::const_iterator cit(m_Delegates.begin());
                cit != m_Delegates.end();
                ++cit)
                (*cit)(_args);
        };

        NotifyEntry& operator += (Delegate _delegate) {m_Delegates.push_back(_delegate); return(*this); };
    }; // eo class NotifyEntry

    std::map<std::string, NotifyEntry*> m_Entries;

public:
    // ctor, dtor, etc....

    // methods
    void register(const std::string& _name);     // register a notification ...
    void unRegister(const std::string& _name);   // unregister it ...

    // Notify interested parties
    void notify(const std::string& _name, const Args& _args) const
    {
        std::map<std::string, NotifyEntry*>::const_iterator cit = m_Entries.find(_name);
        if(cit != m_Entries.end())
           cit.second->raise(_args);
    }; // eo notify

    // Tell the manager we're interested in an event
    void listenFor(const std::string& _name, Delegate _delegate)
    {
        std::map<std::string, NotifyEntry*>::const_iterator cit = m_Entries.find(_name);
        if(cit != m_Entries.end())
            (*cit.second) += _delegate;
    }; // eo listenFor
}; // eo class NotifyManager

我已经遗漏了一些代码,但你明白了。我想这个通知管理器将是一个单身人士。现在,确保尽早创建通知管理器,其他管理员只需在构造函数中注册他们的通知,如下所示:

MyManager::MyManager()
{
    NotificationMananger.getSingleton().register("OnABCUpdated");
    NotificationMananger.getSingleton().register("OnXYZUpdated");
};


AnotherManager::AnotherManager()
{
    NotificationManager.getSingleton().register("TheFoxIsInTheHenHouse");
};

现在,当您的经理需要通知相关方时,它只需要调用notify:

MyManager::someFunction()
{
    CustomArgs args; // custom arguments derived from Args
    NotificationManager::getSingleton().notify("OnABCUpdated", args);
};

其他课程可以听取这些内容。

我意识到我刚刚输入了Observer模式,但我的目的是表明问题在于如何提升这些东西以及它们是否处于const状态。通过从mananager类中抽象出通知的过程,通知的接收者可以自由地修改该经理类。只是没有通知经理。我认为这是公平的。

此外,只有一个地方可以提出通知,这是一个很好的实践,因为它为您提供了一个可以追踪通知的地方。

答案 7 :(得分:0)

我猜您正在关注HICPP或类似的东西。

我们做的是如果我们的代码违反了QACPP并且我们认为它是错误的,那么我们通过Doxygen注意它(通过addtogroup命令以便您轻松获得它们的列表),给出一个理由说明为什么我们违反它然后通过//PRQA命令禁用警告。

答案 8 :(得分:0)

  

请注意,它们不会修改状态   这个对象,所以他们可以   技术上是const方法,   即使他们通常修改   整个系统的状态。在   特别是,侦听器对象可能   回调这个对象并修改   它

由于侦听器可以更改状态,因此该方法不应该是const。根据你所写的内容,听起来你正在使用大量的const_cast并通过指针调用。

答案 9 :(得分:0)

const正确性具有(有意的)传播方式。你应该在任何可以使用它的地方使用const,而const_cast和c-style-casts应该是处理客户端代码的工件 - 永远不会在你的代码中,但非常罕见的例外。

如果void NotifyFooUpdated();调用listeners[all].OnFooUpdated()OnFooUpdated()上的NotifyFooUpdated()不是常量,那么您应该明确限定此突变。如果你的代码始终是const正确的(我正在质疑),那么明确(通过方法声明/监听器访问)你正在改变监听器(成员),然后{{1}}应该有资格作为非const。所以,你只需要将变异声明为尽可能接近源,它应该检出并且const-correctness将正确传播。

答案 10 :(得分:0)

使虚拟函数const始终是一个困难的决定。使它们成为非常量是一种简单的方法。在许多情况下,侦听器函数应该是const:如果它不改变侦听方面(对于此对象)。 如果侦听事件会导致侦听方取消注册(作为一般情况),则此函数应为非const。

虽然对象的内部状态可能会在OnFooChanged调用上发生更改,但在接口级别,下次调用OnFooChanged时,会产生类似的结果。这使它成为常数。

答案 11 :(得分:0)

在类中使用const时,您可以帮助该类的用户了解类与数据的交互方式。你正在订立合同。当您引用const对象时,您知道对该对象进行的任何调用都不会更改其状态。该引用的常量仍然只是与调用者的契约。该对象仍然可以使用可变变量在后台自由地执行一些非const动作。这在缓存信息时特别有用。

例如,您可以使用方法:

int expensiveOperation() const
{
    if (!mPerformedFetch)
    {
        mValueCache = fetchExpensiveValue();
        mPerformedFetch = true;
    }
    return mValueCache;
}

此方法可能需要很长时间才能执行第一次,但会将结果缓存到后续调用。您只需确保头文件将变量completedFetch和valueCache声明为可变。

class X
{
public:
    int expensiveOperation() const;
private:
    int fetchExpensiveValue() const;

    mutable bool mPerformedFetch;
    mutable int mValueCache;
};

这允许const对象与调用者签订合同,并且在后台运行时更聪明一些,就像是const一样。

我建议让你的类将侦听器列表声明为可变,并使其他所有内容尽可能为const。就对象的调用者而言,该对象仍然是const并以此方式起作用。

答案 12 :(得分:0)

Const意味着对象的状态不会被成员函数修改,不多也不少。它与副作用无关。因此,如果我正确理解您的情况,则对象的状态不会更改,这意味着该函数必须声明为const,应用程序的其他部分的状态与此对象无关。即使有时对象状态具有非const子对象(它们不是对象逻辑状态的一部分)(例如互斥体),仍然必须将函数设为const,并且必须将这些部分声明为可变。