我应该将这些成员函数声明为 const 吗?

发布于 2024-09-28 06:48:55 字数 493 浏览 1 评论 0原文

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

void NotifyFooUpdated();

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

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

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

然而,我们的静态代码检查器 QAC 将此标记为偏差,因此我要么必须声明它们 const,要么我必须争论为什么它们应该保持非 const 并为偏差获得资助。

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

I'm working on some C++ code where I have several manager objects with private methods such as

void NotifyFooUpdated();

which call the OnFooUpdated() method on the listeners of this object.

Note that they don't modify the state of this object, so they could technically be made const methods, even though they typically modify the state of the system as a whole. In particular, the listener objects might call back into this object and modify it.

Personally I'd like to leave them as they are and not declare them const.

However, our static code checker QAC flags this as a deviation, so I either have to declare them const, or I have to argue why they should stay non-const and get a grant for the deviation.

What are arguments for not declaring these methods const?
Or should I follow QAC and declare them const?
Should I adopt a strictly local viewpoint restricted to this object, or consider the system as a whole?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

初懵 2024-10-05 06:48:56

有一些反对 const 的好论据,这里是我的看法:- 就我

个人而言,我不会将这些“OnXXXUpdated”作为我的管理器类的一部分。我认为这就是最佳实践存在一些混乱的原因。您正在向感兴趣的各方通知某件事,并且不知道该对象的状态是否会在通知过程中发生变化。可能会,也可能不会。对我来说显而易见的是,通知相关方的过程应该是一个常量。

因此,为了解决这个困境,我会这样做:

从管理器类中删除 OnXXXXUpdated 函数。

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

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

“Delegate”是某种函数指针(例如 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");
};

现在,当您的管理器需要通知感兴趣的各方时,它只需调用通知:

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

其他类可以侦听此内容。

我意识到我刚刚输入了观察者模式,但我的目的是表明问题在于如何引发这些事物以及它们是否处于常量状态。通过从管理器类中抽象出通知过程,通知的接收者可以自由地修改该管理器类。只是不是通知管理器。我认为这是公平的。

此外,恕我直言,有一个地方来发出通知是一个很好的做法,因为它为您提供了一个可以跟踪通知的地方。

There are some good arguments for against const, here so here's my take :-

Personally, I'd not have these "OnXXXUpdated" as part of my manager classes. I think this is why there is some confusion as to best-practice. You're notifying interested parties about something, and do not know whether or not the state of the object is going to change during the notification process. It may, or may not. What is obvious to me, is that the process of notifying interested parties should be a const.

So, to solve this dilemma, this is what I would do:

Get rid of the OnXXXXUpdated functions from your manager classes.

Write a Notification Manager, here's a prototype, with the following assumptions:

"Args" is an arbitrary base-class for passing information when notifications happen

"Delegate" is some kind of a function pointer (e.g 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

I've left some code out as you can probably tell, but you get the idea. I imagine that this Notification Manager would be a singleton. Now, ensuring that the Notification Manager is created early on, the rest of your managers simply register their notifications in their constructor like this:

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


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

Now, when your manager needs to notify interested parties, it simply calls notify:

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

Other classes can listen for this stuff.

I've realised that I've just typed out the Observer pattern, but my intention was to show that the problem is in how these things are being raised and whether they are in a const-state or not. By abstracted the process of notification out of the mananager class, recipients of the notification are free to modify that manager class. Just not the notification manager. I think this is fair.

Besides, having a single place to raise notifications is good pracice imho, as it gives you a single place where you can trace your notifications.

旧时模样 2024-10-05 06:48:56

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

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

I guess you are following HICPP or something similar.

What we do is if our code violates QACPP and we think it is in error then we note it via Doxygen (via addtogroup command so you get a list of them easily), give a reason jusifying why we are violating it and then disable the warning via the //PRQA command.

子栖 2024-10-05 06:48:56

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

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

Note that they don't modify the state
of this object, so they could
technically be made const methods,
even though they typically modify the
state of the system as a whole. In
particular, the listener objects might
call back into this object and modify
it.

Since the listener can change a state, then this method should not be const. From what you wrote, it sounds like you are using lots of const_cast and call through pointers.

黒涩兲箜 2024-10-05 06:48:56

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

如果 void NotifyFooUpdated(); 调用 listeners[all].OnFooUpdated()OnFooUpdated() 不是 const,那么您应该明确限定这种突变。如果您的代码自始至终都是 const 正确的(我对此表示怀疑),那么明确表示(通过方法声明/侦听器访问)您正在改变侦听器(成员),那么 NotifyFooUpdated() 应该符合以下条件:非常量。因此,您只需将突变声明为尽可能接近源,它就会被检查出来,并且常量正确性将正确传播。

const correctness has a (intentionally desirable) way of propagating. you should use const wherever you can get away with it, while const_cast and c-style-casts should be artifacts of dealing with client code - never in your code, but very very rare exceptions.

if void NotifyFooUpdated(); calls listeners[all].OnFooUpdated() while on OnFooUpdated() is not const, then you should explicitly qualify this mutation. if your code is const correct throughout (which i am questioning), then make it explicit (via method declaration/listener access) that you are mutating the listeners (members), then NotifyFooUpdated() should qualify as non-const. so, you just declare the mutation as close to the source as possible and it should check out and const-correctness will propagate properly.

帥小哥 2024-10-05 06:48:56

将虚拟函数设置为常量始终是一个困难的决定。让它们变得非常量是最简单的出路。在许多情况下,侦听器函数应该是 const:如果它不更改侦听方面(对于此对象)。
如果侦听事件会导致侦听方注销自己(作为一般情况),则此函数应该是非常量的。

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

Making virtual functions const is always a difficult decision. Making them non-const is the easy way out. A listener function should be const in many cases: if it doesn't change the listening aspect (for this object).
If listening to an event would cause the listening party to unregister itself (as a general case), then this function should be non-const.

Although the internal state of the object may change on a OnFooChanged call, at the level of the interface, the next time OnFooChanged is called, will have a similar result. Which makes it const.

旧伤还要旧人安 2024-10-05 06:48:56

在类中使用 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 并以这种方式运行。

When using const in your classes you're helping users of that class know how the class will interact with data. You're making a contract. When you have a reference to a const object, you know that any calls made on that object won't change its state. The constness of that reference is still only a contract with the caller. The object is still free to do some non-const actions in the background using mutable variables. This is particularly useful when caching information.

For example you can have class with the method:

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

This method may take a long time to perform the first time, but will cache the result for subsequent calls. You only need to make sure the header file declares the variables performedFetch and valueCache as mutable.

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

    mutable bool mPerformedFetch;
    mutable int mValueCache;
};

This lets a const object make the contract with the caller, and act like it is const while working a little smarter in the background.

I would suggest making your class declare the list of listeners as mutable, and make everything else as const as possible. As far as the object's caller is concerned, the object is still const and acting that way.

半世蒼涼 2024-10-05 06:48:56

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

Const means the state of the object is not modified by the member function, no more, no less. It has nothing to do with side effects. So if I understand your case correctly, the object's state doesn't change that means the function must be declared const, the state of the other parts of your application has nothing to do with this object. Even when sometimes the object state has non-const sub-objects, which are not a part of the object's logical state (e.g. mutexes), the functions still have to be made const, and those parts must be declared mutable.

我的黑色迷你裙 2024-10-05 06:48:55

宽松地说,您有一个容器类:一个充满观察者的管理器。在 C 和 C++ 中,您可以拥有带有非常量值的 const 容器。考虑一下如果您删除了一层包装:

list<Observer> someManager;

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

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

从另一个角度来说:

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

仅当调用者拥有对该对象的唯一引用时,这才是合理的。如果对象是全局的(就像在原始问题中一样)或在线程环境中,则任何给定调用的常量性并不能保证对象的状态在整个调用过程中保持不变。没有副作用并且对于相同输入始终返回相同值的函数是纯函数。 NotifyFooUpdate() 显然不是纯粹的。

Loosely speaking you have a container class: A manager full of observers. In C and C++ you can have const containers with non-const values. Consider if you removed one layer of wrapping:

list<Observer> someManager;

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

You would see nothing strange about a global NotifyFooUpdated taking a const list, since it does not modify the list. That const argument actually makes the argument parsing more permissive: The function accepts both const and non-const lists. All the const annotation on the class method version means is const *this.

To address another perspective:

If you can't guarantee that the object you invoked the function on remains the same before and after the function call, you should generally leave that as non-const.

That's only reasonable if the caller has the only reference to the object. If the object is global (as it is in the original question) or in a threaded environment, the constness of any given call does not guarantee the state of the object is unchanged across the call. A function with no side-effects and which always returns the same value for the same inputs is pure. NotifyFooUpdate() is clearly not pure.

寻找一个思念的角度 2024-10-05 06:48:55

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

如果约定监听器在收到通知时可以更新其状态,则该方法应该是非常量的。

您是说侦听器可以回调对象并修改它。但侦听器不会改变自身 - 因此 Notify 调用可以是 const,但您将指向您自己的对象的非常量指针传递给它。

如果侦听器已经拥有该指针(它只侦听一件事),那么您可以将这两个方法设置为 const,因为修改对象是副作用。正在发生的事情是:

A 呼叫 B
结果 B 修改了 A。

因此,A 调用 B 间接导致其自身的修改,但不是对 self 的直接修改。

如果是这种情况,您的方法可以而且可能应该是 const。

If the listeners are stored as a collection of pointers you can call a non-const method on them even if your object is const.

If the contract is that a listener may update its state when it gets a notification, then the method should be non-const.

You are saying that the listener may call back into the object and modify it. But the listener will not change itself - so the Notify call could be const but you pass a non-const pointer to your own object into it.

If the listener already has that pointer (it listens only to one thing) then you can make both the methods const, as your object getting modified is a side-effect. What is happening is:

A calls B
B modifies A as a result.

So A calling B leads indirectly to its own modification but is not a direct modification of self.

If this is the case both your methods could and probably should be const.

捎一片雪花 2024-10-05 06:48:55

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

您所知道的是,调用它的管理器对象不会改变。然后管理器调用函数的对象可能会改变,也可能不会。你不知道这一点。

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

我想这是一个支持将其设为 const 的论点。

What are arguments for not declaring these methods const?
Or should I follow QAC and declare them const?
Should I adopt a strictly local viewpoint restricted to this object, or consider the system as a whole?

What you know is that that manager object this was called for do not change. The objects the manager then invokes functions on might change or they might not. You don't know that.

From your description I could envision such a design where all involved objects are const (and notifications might be processed by writing them to the console). If you don't make this function const, you prohibit this. If you make it const, you allow both.

I guess this is an argument in favor of making this const.

温柔一刀 2024-10-05 06:48:55

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

If you can't guarantee that the object you invoked the function on remains the same before and after the function call, you should generally leave that as non-const. Think about it - you could write a listener, insert it while the object is non-const, and then use this function to violate const correctness because you once had access to that object when it was non-const in the past. That's wrong.

滴情不沾 2024-10-05 06:48:55

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

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

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

My take on it is that they should remain non const. This is based on my perception that the manager object's state, is actually the aggregate of the states of all the objects it manages plus any intrinsic state i.e., State(Manager) = State(Listener0) + State(Listener1) + ... + State(ListenerN) + IntrinsicState(Manager).

While encapsulation in the source code may belie this run-time relationship. Based on your description, I believe this aggregated state is reflective of the run-time behavior of the program.

To reinforce my argument: I assert that the code should strive to reflect the programs run-time behavior in preference to strict adherence to the exact semantics of compilation.

安人多梦 2024-10-05 06:48:55

const,还是不要 const:这就是问题。

const

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

反对 const 的论点:

  • 这些方法会修改整个系统的状态。
  • 侦听器对象可以修改该对象。

就我个人而言,我倾向于将其保留为 const,事实上它可能会修改整个系统的状态,这与空指针引用非常相似。这是一个 const 方法,它不会修改有问题的对象,但它会使你的程序崩溃,从而修改整个系统的状态。

To const, or not to const: that is the question.

Arguments for const:

  • The methods in question don't modify the state of the object.
  • Your static code checker flags the lack of const as a deviation, maybe you should listen to it.

Arguments against const:

  • The methods modify the state of the system as a whole.
  • Listener objects my modify the object.

Personally I'd go with leaving it as const, the fact that it might modify the state of the system as a whole is pretty much like a null pointer reference. It's a const method, it doesn't modify the object in question, but it will crash your program thereby modifying the state of the whole system.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文