2010-10-19 34 views
13

我工作的一些C++代码,我有私人的方式,如我应该声明这些方法为const吗?

void NotifyFooUpdated(); 

它呼吁该对象的听众OnFooUpdated()方法几个经理的对象。

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

个人而言,我想离开他们,因为他们不声明它们const。但是,我们的静态代码检查器QAC会将其标记为偏差,所以我必须声明他们const,或者我必须争论他们为什么应该保持非常量并获得偏差授予。

什么是不声明这些方法const论点?
或者我应该关注QAC并声明他们const
我是否应该严格限制这个对象的本地观点,还是将整个系统看作是一个整体?

回答

3

松散地说,你有一个容器类:一个充满观察者的经理。在C和C++中,你可以使用非const值的const容器。试想,如果你删除包裹的一层:

list<Observer> someManager; 

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

你会看到什么奇怪的全局NotifyFooUpdated采取一个const名单,因为它不修改该列表。该const参数实际上使参数解析更宽松:该函数接受const和非const列表。类方法版本上的所有const注释的意思是const *this

为了解决另一个角度来看:

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

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

+0

我想你是赞成声明方法const? – starblue 2010-10-27 19:33:53

+2

如果这是代码审查,我可能不会反对任何方式,除非有其他因素在起作用。如果每个人都访问全局,并且没有传递const引用,那真的没关系。我坚决不同意静态分析可以告诉你这个方法应该是const的前提:静态分析告诉你它*可以*是const *被实现的*,但它不知道你是否打算稍后增强函数(例如与统计,或消息排队或重复删除或你有什么)。 – 2010-10-27 21:02:06

0

我想你是跟随HICPP或类似的东西。

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

+0

是的,从技术上讲,它在这里是类似的,问题是该怎么做,以及什么原因。另外,我不是那种支持偏差的人,所以我需要对与我的同事争论的问题有足够的了解。 – starblue 2010-10-19 12:57:26

2

什么是不声明这些方法的参数const
或者我应该关注QAC并声明他们const
我是否应该严格限制这个对象的本地观点,还是将整个系统看作是一个整体?

你知道的是,这位经理反对这被称为用于做变化。管理员然后调用上的函数的对象可能会更改,或者他们可能不会。你不知道。

从你的描述我可以设想这样的设计,所有涉及的对象是const(和通知可能将其写入控制台进行处理)。如果您不具备此功能const,则禁止此操作。如果您制作了const,则允许两者。

我想这是有利于使这个const的参数。

+0

我认为这会比较奇怪,因为所有的经理都是非常量的。还要注意这些方法是私有的(我编辑了这个问题),通常调用它们的方法会修改对象的状态。 – starblue 2010-10-19 13:08:34

3

如果侦听器存储为指针集合,则即使对象为常量,也可以在其上调用非const方法。

如果合同是,当它得到一个通知侦听器可以更新其状态,则该方法应该是非常量。

您是说监听器可能会回调对象并对其进行修改。但是监听器不会自己改变 - 所以Notify调用可以是const,但是你传递一个非const指针给你自己的对象。

如果监听器已经有了这个指针(它只监听一件事),那么你可以使两个方法都是const,因为你的对象被修改是一种副作用。发生的情况是:

A调用B B因此修改A.

因此,一个调用B会间接导致它自己的修改,但不是对自我的直接修改。

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

+0

是的,从技术上讲,可以使方法为常量,但它也是很好的“设计”?你想说尽可能地做一些事情总是很好吗?顺便说一句,监听器被存储为非常量指针,并且通常会自行更改。 – starblue 2010-10-19 13:18:47

1

如果你不能保证你调用函数的对象在函数调用前后保持不变,你通常应该保持非const。考虑一下 - 你可以编写一个监听器,在对象是非const的时候插入它,然后使用这个函数来违反const的正确性,因为过去它曾经是非const类型的对象时曾经访问过这个对象。这是错误的。

0

注意,它们不会修改状态时,该对象的 ,所以他们可以 技术上进行const的方法, 尽管他们通常修改系统的 国家作为一个整体。特别是,在 特定的情况下,监听器对象可能会调用 这个对象,并修改它 它。

由于侦听器可以改变状态,所以这个方法不应该是const。从你写的内容来看,这听起来像你正在使用大量的const_cast并通过指针调用。

+0

不,没有涉及'const_cast',其他对象具有用于与此对象交互的非const指针。 – starblue 2010-10-26 06:35:17

+0

只要你的方法改变了某些东西,就不应该声明为const。就那么简单。 – 2010-10-26 20:09:02

0

const正确性有一个(故意要求的)传播方式。你应该尽量使用const,而const_cast和c-style-casts应该是处理客户端代码的工件 - 绝不在你的代码中,但是非常罕见的例外。

如果void NotifyFooUpdated();调用listeners[all].OnFooUpdated()OnFooUpdated()不是常量,那么您应该明确限定此突变。如果你的代码在整个过程中都是const正确的(我在质疑),然后使它明确(通过方法声明/侦听器访问)你正在改变监听器(成员),那么NotifyFooUpdated()应该被视为非const。所以,你只要将变异声明为尽可能靠近源,并且它应该检出并且const正确性将正确地传播。

0

使虚拟函数为const通常是一个困难的决定。让它们变成非const是简单的方法。在很多情况下,侦听器函数应该是const:如果它不改变侦听方面(对于这个对象)。 如果收听某个事件会导致聆听方取消自己的注册(如一般情况),那么该函数应该是非const的。

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

0

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

例如,你可以有类方法:

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

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

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

    mutable bool mPerformedFetch; 
    mutable int mValueCache; 
}; 

这让一个const对象与调用者建立契约,并且在后台工作时稍微聪明些。

我会建议让你的类声明监听器的列表为可变的,并尽可能使其他所有的东西变为const。就对象的调用者而言,该对象仍然是const并且是这样的。

1

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

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

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

1

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

参数为const

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

参数针对const

  • 的方法修改该系统的状态作为一个整体。
  • 监听器对象我修改了对象。

就我个人而言,我会把它作为const,它可能会修改整个系统的状态非常像空指针引用。这是一个const方法,它不会修改有问题的对象,但会导致程序崩溃,从而修改整个系统的状态。

1

有针对常量一些很好的理由,这里所以这里是我的看法: -

个人而言,我不会有这些“OnXXXUpdated”作为我的经理类的一部分。我认为这就是为什么最佳实践存在一些困惑。您正在通知感兴趣的各方有关某事的信息,并且不知道在通知过程中对象的状态是否会发生变化。它可能,也可能不会。什么对我来说很明显,是通知感兴趣方应该是一个常量的过程。

因此,要解决这一难题,这是我会做什么:

从你的管理类摆脱OnXXXXUpdated功能。

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

“参数数量”是任意基类出于传递信息通知时发生

“委派”是某种一个函数指针(例如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); 
}; 

其他类可以听这个东西。

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

此外,有一个单一的地方来提高通知是好的实践,因为它给你一个地方,你可以跟踪你的通知。

0

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

相关问题