Должен ли я объявлять эти методы const?
Я работаю над некоторым кодом на С++, где у меня есть несколько объектов менеджера с частными методами, такими как
void NotifyFooUpdated();
которые вызывают метод OnFooUpdated()
для слушателей этого объекта.
Обратите внимание, что они не изменяют состояние этого объекта, поэтому технически могут быть сделаны методы const
, даже если они обычно изменяют состояние системы в целом. В частности, объекты-слушатели могут вернуться к этому объекту и изменить его.
Лично я хотел бы оставить их такими, какие они есть, и не объявлять их const
.
Однако наша статическая проверка кода QAC обозначает это отклонение, поэтому я либо должен объявить их const
, либо я должен спорить, почему они должны оставаться непостоянными и получать грант для отклонения.
Что такое аргументы для не объявления этих методов const
?
Или я должен следовать QAC и объявлять их const
?
Должен ли я использовать строго локальную точку зрения, ограниченную этим объектом, или рассматривать систему в целом?
Ответы
Ответ 1
Говоря кратко, у вас есть класс контейнеров: менеджер, полный наблюдателей. В C и С++ вы можете иметь контейнеры const с неконстантными значениями. Подумайте, удалили ли вы один слой обертки:
list<Observer> someManager;
void NotifyFooUpdated(const list<Observer>& manager) { ... }
Вы не увидите ничего странного в глобальном NotifyFooUpdated, принимающем список const, так как он не изменяет список. Этот аргумент const фактически делает аргумент parsing более разрешительным: функция принимает как константные, так и неконстантные списки. Все аннотации const для версии метода класса означают const *this
.
Чтобы рассмотреть другую перспективу:
Если вы не можете гарантировать, что объект, который вы вызывали, функция остается прежней до и после вызова функции, вы должны оставить это как не const.
Это разумно, если вызывающая сторона имеет единственную ссылку на объект. Если объект является глобальным (как в исходном вопросе) или в потоковой среде, то константа любого данного вызова не гарантирует, что состояние объекта не изменяется во время вызова. Функция, не имеющая побочных эффектов и которая всегда возвращает одно и то же значение для одних и тех же входов, является чистой. NotifyFooUpdate() явно не является чистым.
Ответ 2
Если слушатели хранятся в виде набора указателей, вы можете вызвать на них неконсольный метод, даже если ваш объект const.
Если контракт заключается в том, что слушатель может обновить свое состояние при получении уведомления, то метод должен быть неконстантным.
Вы говорите, что слушатель может вернуться в объект и изменить его. Но слушатель не будет меняться сам, поэтому вызов Notify может быть const, но вы передаете ему неконтекстный указатель на свой собственный объект.
Если слушатель уже имеет этот указатель (он слушает только одну вещь), тогда вы можете сделать оба метода const, так как ваш объект, получивший изменение, является побочным эффектом. Что происходит:
A вызывает B
B изменяет A как результат.
Таким образом, вызов B ведет косвенно к его собственной модификации, но не является прямой модификацией самого себя.
Если это так, ваши методы могут и, вероятно, должны быть const.
Ответ 3
Каковы аргументы для не объявления этих методов const
?
Или я должен следовать QAC и объявлять их const
?
Должен ли я использовать строго локальную точку зрения, ограниченную этим объектом, или рассматривать систему в целом?
Что вы знаете, так это то, что этот объект-менеджер, который был вызван, не изменяется. Объекты, которые диспетчер затем вызывает функции, могут измениться или они могут и не быть. Вы этого не знаете.
Из вашего описания я мог бы представить такую конструкцию, где все задействованные объекты const
(и уведомления могут быть обработаны путем записи их на консоль). Если вы не выполняете эту функцию const
, вы запрещаете это. Если вы сделаете это const
, вы разрешите оба.
Я думаю, это аргумент в пользу создания этого const
.
Ответ 4
Если вы не можете гарантировать, что объект, который вы вызывали, функция остается прежней до и после вызова функции, вы должны оставить это как неконстантное. Подумайте об этом - вы можете написать слушателя, вставить его, пока объект не является константой, а затем использовать эту функцию, чтобы нарушить корректность const, потому что вы когда-то имели доступ к этому объекту, когда он был неконстантным в прошлом. Это неправильно.
Ответ 5
Я считаю, что они должны оставаться не const. Это основано на моем восприятии состояния объекта-менеджера, на самом деле является совокупностью состояний всех объектов, которыми он управляет, плюс любое внутреннее состояние, т.е. State(Manager) = State(Listener0) + State(Listener1) + ... + State(ListenerN) + IntrinsicState(Manager)
.
В то время как инкапсуляция в исходном коде может противоречить этим отношениям времени выполнения. Основываясь на вашем описании, я считаю, что это агрегированное состояние отражает поведение программы во время выполнения.
Чтобы усилить мой аргумент: я утверждаю, что код должен стремиться отражать поведение времени выполнения программ, предпочитая строгое соблюдение точной семантики компиляции.
Ответ 6
К const
, или не к const
: это вопрос.
Аргументы для const
:
- Эти методы не изменяют состояние объекта.
- Ваша статическая проверка кода означает отсутствие const в качестве отклонения, возможно, вам следует его прослушать.
Аргументы против const
:
- Методы изменяют состояние системы в целом.
- Объекты прослушивателя меняют объект.
Лично я бы оставил его как const
, тот факт, что он может изменить состояние системы в целом, в значительной степени похож на ссылку с нулевым указателем. Это метод const
, он не изменяет объект, о котором идет речь, но он разрушит вашу программу, тем самым изменив состояние всей системы.
Ответ 7
Есть несколько хороших аргументов в отношении const, вот так вот мое взятие: -
Лично у меня не было бы этих "OnXXXUpdated" как часть моих классов менеджера. Я думаю, именно поэтому существует некоторая путаница в отношении лучшей практики. Вы уведомляете заинтересованные стороны о чем-то и не знаете, будет ли состояние объекта изменяться во время процесса уведомления. Он может или не может. Для меня очевидно, что процесс уведомления заинтересованных сторон должен быть 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
Я оставил код, как вы, вероятно, можете сказать, но вы поняли. Я предполагаю, что этот диспетчер уведомлений будет синглом. Теперь, гарантируя, что Менеджер уведомлений создается на ранней стадии, остальные ваши менеджеры просто регистрируют свои уведомления в своем конструкторе следующим образом:
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, но я хотел показать, что проблема заключается в том, как возникают эти вещи и находятся ли они в состоянии const или нет. Исчерпывая процесс уведомления из класса mananager, получатели уведомления могут изменять этот класс менеджера. Просто не менеджер уведомлений. Я думаю, это справедливо.
Кроме того, наличие единого места для оповещения уведомлений - это хороший pracice imho, поскольку он дает вам одно место, где вы можете отслеживать свои уведомления.
Ответ 8
Я предполагаю, что вы следуете за HICPP или что-то подобное.
Что мы делаем, так это если наш код нарушает QACPP, и мы считаем, что это ошибка, мы отмечаем его через Doxygen (с помощью команды addtogroup, чтобы вы могли легко получить список из них), объясните, почему мы это нарушаем, а затем отключите предупреждение с помощью команды //PRQA
.
Ответ 9
Обратите внимание, что они не изменяют состояние этого объекта, чтобы они могли технически быть выполненными методами const, даже если они обычно изменяют состояния системы в целом. В в частности, объекты-слушатели могут перезвонить в этот объект и изменить он.
Так как слушатель может изменить состояние, то этот метод не должен быть const. Из того, что вы написали, похоже, что вы используете множество const_cast и вызываете указатели.
Ответ 10
const корректность имеет (намеренно желаемый) способ распространения. вы должны использовать const, где бы вы ни скрывались, в то время как const_cast и c-style-casts должны быть артефактами для работы с клиентским кодом - никогда в вашем коде, но очень редкие исключения.
если void NotifyFooUpdated();
вызывает listeners[all].OnFooUpdated()
, а на OnFooUpdated()
не const, то вы должны явно квалифицировать эту мутацию. если ваш код является константным во всем (что я допрашиваю), тогда сделайте его явным (через объявление метода/доступ слушателя), что вы мутируете слушателей (участников), тогда NotifyFooUpdated()
должен квалифицироваться как неконстантный. поэтому вы просто объявляете мутацию как можно ближе к источнику, и она должна проверяться, и const-correctness будет правильно распространяться.
Ответ 11
Создание виртуальных функций const всегда является трудным решением. Сделать их неконстантными - это легкий выход. Функция прослушивателя во многих случаях должна быть const: если она не меняет аспект прослушивания (для этого объекта).
Если прослушивание события приведет к тому, что слушающая сторона сама отменит регистрацию (в общем случае), эта функция должна быть неконстантной.
Хотя внутреннее состояние объекта может измениться при вызове OnFooChanged, на уровне интерфейса при следующем вызове OnFooChanged будет получен аналогичный результат. Это делает его const.
Ответ 12
При использовании const в ваших классах вы помогаете пользователям этого класса знать, как класс будет взаимодействовать с данными. Вы заключаете контракт. Когда у вас есть ссылка на объект const, вы знаете, что любые вызовы, сделанные на этом объекте, не изменят его состояние. Сознание этой ссылки остается только контрактом с вызывающим. Объект по-прежнему может выполнять некоторые неконстантные действия в фоновом режиме с использованием изменяемых переменных. Это особенно полезно при кэшировании информации.
Например, вы можете иметь класс с помощью метода:
int expensiveOperation() const
{
if (!mPerformedFetch)
{
mValueCache = fetchExpensiveValue();
mPerformedFetch = true;
}
return mValueCache;
}
Этот метод может занять много времени, чтобы выполнить первый раз, но будет кэшировать результат для последующих вызовов. Вам нужно только убедиться, что файл заголовка объявляет переменные doneFetch и valueCache как изменяемые.
class X
{
public:
int expensiveOperation() const;
private:
int fetchExpensiveValue() const;
mutable bool mPerformedFetch;
mutable int mValueCache;
};
Это позволяет const-объекту заключить контракт с вызывающим, и действовать так же, как это const, при работе немного умнее в фоновом режиме.
Я бы предложил сделать ваш класс объявленным списком слушателей как изменчивым и сделать все остальное как можно более const. Что касается вызывающего объекта, объект все еще const и действует таким образом.
Ответ 13
Константа означает, что состояние объекта не изменяется функцией-членом, не более, не менее. Это не имеет никакого отношения к побочным эффектам. Поэтому, если я правильно понимаю ваш случай, состояние объекта не изменяется, это означает, что функция должна быть объявлена const, состояние других частей вашего приложения не имеет ничего общего с этим объектом. Даже когда иногда состояние объекта имеет неконстантные под-объекты, которые не являются частью логического состояния объекта (например, мьютексы), функции все равно должны быть сделаны const, и эти части должны быть объявлены изменяемыми.