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

void NotifyFooUpdated();

哪个调用 OnFooUpdated() 该对象的侦听器上的方法。

请注意,它们不会修改该对象的状态,因此从技术上讲它们可以被制作 const 方法,尽管它们通常会修改整个系统的状态。特别是,侦听器对象可能回调该对象并修改它。

就我个人而言,我想让它们保持原样而不声明它们 const.

然而,我们的静态代码检查器 QAC 将此标记为偏差,所以我要么必须声明它们 const, ,或者我必须争论为什么他们应该保持非常量并为偏差获得补助金。

不声明这些方法的理由是什么 const?
或者我应该遵循 QAC 并声明它们 const?
我应该采取仅限于此对象的严格局部观点,还是将系统视为一个整体?

有帮助吗?

解决方案

宽松地说,您有一个集装箱类:一个充满观察者的经理。在C和C ++中,您可以具有具有非CONST值的常量容器。考虑一下您是否删除了一层包装:

list<Observer> someManager;

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

您不会看到一个全局通知汇总列表,因为它不会修改列表。该const参数实际上使该参数解析更加宽松:该函数同时接受const和非const列表。类方法版本上的所有const注释表示是 const *this.

解决另一个观点:

如果您不能保证在函数调用之前和之后调用该函数的对象保持相同,则通常应将该函数作为非const。

只有呼叫者唯一对对象的引用时,这是合理的。如果对象是全局(如原始问题中的)或螺纹环境中的,那么任何给定调用的构成都不能保证对象的状态在整个呼叫中没有变化。没有副作用的功能,并且总是返回相同输入的相同值是 纯的. 。 notifyfooupdate()显然不是纯净的。

其他提示

如果听众被存储为指针的集合,即使您的对象是const,也可以在他们上调用非const方法。

如果合同是在收到通知时可以更新其状态的合同,则该方法应无关。

您是说听众可以回到对象中并修改对象。但是听众不会自行改变 - 因此,通知呼叫可以是const,但是您将非范围的指针传递给了自己的对象。

如果侦听器已经具有该指针(仅听一件事),那么您可以将这两个方法const做,因为您的对象被修改为副作用。发生的是:

A调用B B会导致A修改。

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

如果是这种情况,则您的两种方法都可以并且可能是const。

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

您知道的是那个经理对象被要求做 不是 改变。然后,管理器然后调用函数的对象 可能 更改否则他们可能不会。你不知道。

从您的描述中,我可以设想所有涉及对象的设计 const (并且可以通过将其写入控制台来处理通知)。如果您不做此功能 const, ,您禁止这一点。如果你做到了 const, ,你们允许两者。

我想这是一个支持这样做的论点 const.

如果您不能保证在函数调用之前和之后调用该函数的对象保持相同,则通常应将该函数作为非const。考虑一下 - 您可以编写一个侦听器,在对象是非const时插入它,然后使用此函数违反const正确性,因为过去您曾经在该对象中曾经访问过该对象。那是错误的。

我的看法是他们应该留下来 非const. 。这是基于我的看法,即经理对象的状态实际上是其管理的所有对象的总汇总以及任何固有状态,即 State(Manager) = State(Listener0) + State(Listener1) + ... + State(ListenerN) + IntrinsicState(Manager).

尽管源代码中的封装可能会相信这种运行时关系。根据您的描述,我相信这种汇总状态反映了程序的运行时间行为。

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

const, 或不 const: : 就是那个问题。

争论 const:

  • 所讨论的方法不会修改对象的状态。
  • 您的静态代码检查器标志缺乏const作为偏差,也许您应该听。

反对的争论 const:

  • 该方法修改了整个系统的状态。
  • 侦听器对我的对象进行对象。

我个人会留给它 const, ,它可能会修改整个系统状态的事实几乎就像一个空指针参考。它是 const 方法,它不会修改所讨论的对象,但是它将崩溃您的程序,从而修改整个系统的状态。

有一些很好的论据 const, ,这是我的看法: -

就我个人而言,作为我的经理课程的一部分,我没有这些“ onxxxupded”。我认为这就是为什么最佳实践有些混乱的原因。您将通知感兴趣的方有关某些事情,并且不知道对象的状态在通知过程中是否会更改。它可能或可能不会。什么 对我来说很明显,是通知感兴趣的人的过程 应该 成为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

我可能会发现一些代码,但是您明白了。我想这位通知经理将是单身人士。现在,确保Notification Manager是早期创建的,其余经理只需在其构造函数中注册其通知:

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


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

现在,当您的经理需要通知感兴趣的各方时,它只会致电通知:

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

其他课程可以听这些东西。

我已经意识到我刚刚键入了观察者模式,但是我的意图是证明问题在于如何提出这些东西以及它们是否处于const-state中。通过抽象从Mananager类中的通知过程中,通知的收件人可以自由修改该管理器类。只是通知经理。我认为这是公平的。

此外,有一个单个地方提出通知是imho的良好pracice,因为它为您提供了一个可以追踪通知的地方。

我想您正在关注HICPP或类似的事情。

我们要做的是,如果我们的代码违反qacpp,并且我们认为它是错误的,那么我们通过doxygen(通过addTogroup命令,使您轻松获取列表),请给出一个理由,说明为什么我们违反它,然后禁用警告。通过 //PRQA 命令。

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

由于听众可以更改状态,因此该方法不应是const。从您写的内容来看,听起来您正在使用很多const_cast并通过指针打电话。

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

如果 void NotifyFooUpdated(); 呼叫 listeners[all].OnFooUpdated() 上面 OnFooUpdated() 不是const,那么您应该明确鉴定此突变。如果您的代码在整个过程中都正确(我正在质疑),则将其显式(通过方法声明/侦听器访问)您正在突变听众(成员),然后 NotifyFooUpdated() 应该有资格为非企业。因此,您只需声明该突变尽可能靠近来源,并且应该检查并纠正率会正确传播。

制作虚拟函数const始终是一个艰难的决定。使它们非企业是简单的出路。在许多情况下,侦听器函数应该是const:如果它不更改听力方面(对于此对象)。如果聆听事件会导致听力聚会自我注册(作为一般情况),那么此功能应该是非const的。

尽管对象的内部状态可能会在接口的调用上发生变化,但是在接口的级别上,下次被调用时,将会有类似的结果。这使它成为const。

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

例如,您可以使用以下方法进行类:

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

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

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

    mutable bool mPerformedFetch;
    mutable int mValueCache;
};

这让 const 对象与调用者签订契约,并像 const 一样运行,同时在后台更智能地工作。

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

const表示对象的状态未通过成员函数修改, ,不再,也不再。它与副作用无关。因此,如果我正确理解您的案例,则对象的状态不会更改,这意味着必须声明该函数const,应用程序的其他部分的状态与此对象无关。即使有时对象状态具有非const子对象,这不是对象逻辑状态(例如静音)的一部分,但仍必须制作函数,并且必须声明这些部分可变。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top