Должен ли я объявить эти методы const? - PullRequest
13 голосов
/ 19 октября 2010

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

void NotifyFooUpdated();

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

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

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

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

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

Ответы [ 13 ]

3 голосов
/ 19 октября 2010

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

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

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

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

А звонит Б B модифицирует A в результате.

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

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

3 голосов
/ 27 октября 2010

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

list<Observer> someManager;

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

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

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

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

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

2 голосов
/ 19 октября 2010

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

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

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

Полагаю, это аргумент в пользу принятия const.

1 голос
/ 29 октября 2010

Есть несколько хороших аргументов против const , вот мой выбор: -

Лично у меня не было бы этих "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");
};

Теперь, когда вашему менеджеру нужно уведомить заинтересованные стороны, он просто вызываетnotify:

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

Другие классы могут прослушивать этот материал.

Я понял, что только что набрал шаблон Observer, но я хотел показать, что проблемав том, как эти вещи поднимаются и находятся ли они в постоянном состоянии или нет.Абстрагировав процесс уведомления от класса mananager, получатели уведомления могут свободно изменять этот класс менеджера.Просто не менеджер уведомлений.Я думаю, что это справедливо.

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

1 голос
/ 29 октября 2010

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

Аргументы для const:

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

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

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

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

1 голос
/ 28 октября 2010

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

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

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

1 голос
/ 23 октября 2010

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

0 голосов
/ 30 октября 2010

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

0 голосов
/ 27 октября 2010

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

Например, у вас может быть класс с методом:

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

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

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

    mutable bool mPerformedFetch;
    mutable int mValueCache;
};

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

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

0 голосов
/ 26 октября 2010

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

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

...