Должен ли я объявить эти методы const?

Я работаю над некоторым кодом C++, где у меня есть несколько объектов manager с частными методами, такими как

void NotifyFooUpdated();

, которую называют OnFooUpdated() метод на слушателей данного объекта.

обратите внимание, что они не изменяют состояние этого объекта, так что они могут быть технически составила const методы, хотя они обычно изменяют состояние системы в целом. В частности, объекты прослушивателя могут вызывать этот объект и изменять он.

лично я хотел бы оставить их такими, какие они есть, и не объявлять их const.

однако наша статическая проверка кода qac помечает это как отклонение, поэтому я либо должен объявить их const, или я должен спорить, почему они должны оставаться неконст и получать грант за отклонение.

каковы аргументы для не объявления этих методов const?
Или я должен следовать QAC и объявить их const?
Должен ли я принять строго локальную точку зрения ограничимся этим объектом или рассмотрим систему в целом?

13 ответов


грубо говоря, у вас есть класс-контейнер: менеджер, полный наблюдателей. В C и C++ вы можете иметь константный контейнеры с константным значениям. Рассмотрим, если вы удалили один слой упаковки:

list<Observer> someManager;

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

вы не увидите ничего странного в глобальном NotifyFooUpdated принимая список const, так как он не изменяет список. Этот аргумент const фактически делает разбор аргументов более разрешительным: функция принимает как списки const, так и списки non-const. Все const аннотации на версии метода класса означает const *this.

чтобы обратиться к другой перспективе:

Если вы не можете гарантировать, что объект, на который вы вызвали функцию, остается неизменным до и после вызова функции, вы обычно должны оставить это как non-const.

это разумно, только если вызывающий объект имеет единственную ссылку на объект. Если объект является глобальным (как в исходном вопросе) или в потоковой среде, константа любой данный вызов не гарантирует, что состояние объекта не изменяется в вызове. Функция без побочных эффектов, которая всегда возвращает одно и то же значение для одних и тех же входных данных -чисто. NotifyFooUpdate () явно не является чистым.


Если прослушиватели хранятся как коллекция указателей, вы можете вызвать метод non-const на них, даже если ваш объект const.

Если контракт заключается в том, что прослушиватель может обновить свое состояние, когда он получает уведомление, то метод должен быть non-const.

вы говорите, что слушатель может перезвонить в объект и изменить его. Но слушатель не изменится сама по себе - так сообщим вызов может быть константной, но вы проходите не константный указатель на свой объект в нее.

Если у слушателя уже есть этот указатель (он слушает только одну вещь), то вы можете сделать оба метода const, так как ваш объект модифицируется как побочный эффект. Что происходит:

a вызывает B B изменяет A в результате.

таким образом, вызов B косвенно приводит к его собственной модификации, но не является прямой модификацией себя.

Если это так, то оба ваших метода могут и, вероятно, должны быть const.


каковы аргументы для не объявления этих методов const?
Или я должен следовать QAC и объявить их const?
Должен ли я принять строго локальную точку зрения, ограниченную этим объектом, или рассматривать систему в целом?

вы знаете, что этот объект менеджера был вызван для do не изменить. Объекты, которые менеджер затем вызывает функции на может изменить или они не могли. Вы не знаете что.

из вашего описания я мог бы представить себе такой дизайн, где все вовлеченные объекты const (и уведомления могут быть обработаны путем записи их на консоль). Если вы не сделаете эту функцию const, вам запрещают это. Если вы сделаете это const, вы оба.

Я думаю, это аргумент в пользу того, чтобы сделать это const.


Если вы не можете гарантировать, что объект, на который вы вызвали функцию, остается неизменным до и после вызова функции, вы обычно должны оставить это как неконст. Подумайте об этом - вы можете написать прослушиватель, вставить его, пока объект не является const, а затем использовать эту функцию для нарушения правильности const, потому что у вас когда-то был доступ к этому объекту, когда он не был const в прошлом. Это неправильно.


Я считаю, что они должны остаться non const. Это основано на моем восприятии, что состояние объекта менеджера на самом деле является совокупностью состояний всех объектов, которыми он управляет, плюс любое внутреннее состояние, т. е. State(Manager) = State(Listener0) + State(Listener1) + ... + State(ListenerN) + IntrinsicState(Manager).

в то время как инкапсуляция в исходном коде может противоречить этому отношению времени выполнения. Основываясь на вашем описании, я считаю, что это агрегированное состояние отражает поведение программы во время выполнения.

для того чтобы усилить мои аргумент: я утверждаю, что код должен стремиться отражать поведение программы во время выполнения, а не строгое соблюдение точной семантики компиляции.


до const или не const: вот в чем вопрос.

Аргументы const:

  • рассматриваемые методы не изменяют состояние объекта.
  • ваш статический код проверки флаги отсутствие const как отклонение, возможно, вы должны слушать его.

аргументы против const:

  • методы изменяют состояние системы как целый.
  • слушатель возражает мое изменение объекта.

лично я бы пошел, оставив его как const, тот факт, что он может изменить состояние системы в целом очень похож на нулевой указатель ссылки. Это const метод не изменяет объект, но это приведет к краху вашей программы, тем самым изменяя состояние всей системы.


есть несколько хороших аргументов против const, вот так вот взять :-

лично у меня не было бы этих "OnXXXUpdated" как часть моих классов менеджера. Я думаю, именно поэтому существует некоторая путаница в отношении наилучшей практики. Вы уведомляете заинтересованные стороны о чем-то и не знаете, изменится ли состояние объекта во время процесса уведомления. Может, да, а может, и нет. Что?!--8-->и для меня очевидно, что процесс уведомления заинтересованных сторон должны будьте const.

Итак, чтобы решить эту дилемму, вот что я бы сделал:

избавьтесь от функций OnXXXXUpdated из ваших классов менеджера.

напишите диспетчер уведомлений, вот прототип, со следующими предположениями:

"Args" - произвольный базовый класс для передачи информации, когда происходят уведомления

"делегат" - это своего рода указатель функции (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

Я оставил код, как вы, вероятно, можете сказать, но вы поняли идею. Я предполагаю, что этот менеджер уведомлений будет одноэлементным. Теперь, гарантируя, что диспетчер уведомлений создан на раннем этапе, остальные ваши менеджеры просто регистрируют свои уведомления в своем конструкторе следующим образом:

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 или нет. Абстрагировав процесс уведомления из класса mananager, получатели уведомления могут изменять этот класс диспетчера. Только не диспетчер уведомлений. Я думаю, это справедливый.

кроме того, наличие одного места для повышения уведомлений-хорошая практика imho, поскольку она дает вам одно место, где вы можете отслеживать свои уведомления.


Я думаю, вы следуете HICPP или что-то подобное.

что мы делаем, если наш код нарушает QACPP, и мы думаем, что это ошибка, то мы отмечаем это через Doxygen (через команду addtogroup, чтобы вы легко получили список из них), дайте причину, почему мы нарушаем его, а затем отключите предупреждение через .


обратите внимание, что они не изменяют состояние этого предмета, чтобы они могли технически быть сделаны const методы, хотя они, как правило, изменить состояние системы в целом. В в частности, объекты listener могут вызовите этот объект и измените он.

поскольку прослушиватель может изменить состояние, этот метод не должен быть const. Из того, что вы написали, похоже, что вы используете много const_cast и call through указатели.


корректность const имеет (намеренно желательный) способ распространения. вы должны использовать const везде, где вы можете уйти с ним, в то время как const_cast и C-style-casts должны быть артефактами работы с клиентским кодом - никогда в вашем коде, но очень редкие исключения.

если void NotifyFooUpdated(); звонки listeners[all].OnFooUpdated() на OnFooUpdated() не const, тогда вы должны явно квалифицировать эту мутацию. если ваш код является const правильным во всем (что я ставлю под сомнение), то сделайте его явным (с помощью метода объявление / доступ прослушивателя), что вы мутируете слушателей (членов), то NotifyFooUpdated() должен квалифицироваться как non-const. таким образом, вы просто объявляете мутацию как можно ближе к источнику, и она должна проверить и const-правильность будет распространяться правильно.


создание виртуальных функций const всегда является сложным решением. Сделать их не-const-это простой выход. Функция прослушивателя должна быть const во многих случаях: если она не изменяет аспект прослушивания (для этого объекта). Если слушать на событие вызовет прослушивании отменить себя (в общем случае), то эта функция должна быть константным.

хотя внутреннее состояние объекта может изменяться при вызове OnFooChanged, на уровне интерфейса в следующий раз OnFooChanged называется, будет иметь аналогичный результат. Что делает его 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, и эти части должны быть объявлены Мутабельный.