Реализация сигналов (шаблон наблюдателя): возможно ли изменение или const_cast?

Я реализую свой собственный механизм signal/slot (шаблон наблюдателя, стиль Qt), поэтому я могу иметь property, который уведомляет... stuff... that is changed.

Я думаю, что С++ 11 обеспечивает все необходимое, чтобы сделать очень сукцинтами и возможную реализацию. "Проблема", с которой я сталкиваюсь, - это если я хочу "подключиться" к сигналу объекта const, мне нужна функция signal::connect, которая будет const, но изменит список обратных вызовов/наблюдателей. Есть два простых способа исправить это:

  • const_cast списки внутри connect.
  • Сделайте списки mutable.

Оба кажутся мне похожими на то же (и это было задано до, например, в этом вопросе), и совершенно прекрасно логически, но стилистически сомнительно. Отсюда вопрос. Есть ли способ обойти это или это действительно оправданное использование const_cast/mutable?

Некоторый предварительный код, который у меня есть сейчас:

template<typename... ArgTypes>
class signal
{
public:
  template<typename Callable>
  void connect(Callable&& callback) const
  {
    std::lock_guard<std::mutex> lock(slots_mutex);
    slots.emplace_back(callback);
  }

  void emit(ArgTypes... arguments) const
  {
    std::lock_guard<std::mutex> lock(slots_mutex);
    for(auto&& callback : slots)
    {
      callback(arguments...);
    }
  }

private:
  // mutable here allows to connect to a const object signals
  mutable std::vector<std::function<void(ArgTypes...)>> slots;
  std::mutex slots_mutex;

};

Примечание. Я не тестировал этот код; это всего лишь отражение моего нынешнего состояния ума.

Ответ 1

mutable обычно является лучшим выбором для таких случаев.

Избегайте (const) кастинга, когда можете, он склонен к поведению undefined, а mutable гарантированно не 1).


1mutable члены класса гарантированно не будут отправляться на выделенные коды .text. Например,

Ответ 2

Есть два простых способа исправить это:

  • const_cast списки внутри connect.
  • Сделайте списки mutable.

На самом деле существует третий выбор (который является обходным путем общего назначения, если С++ не предоставил ключевое слово mutable) - вы можете переместить соответствующие данные из синтаксического объекта:

class X
{
    mutable int           i1_;

    // Data pointed to by i2_ semantically belongs to this object
    // but doesn't constitute a syntactical part of it so it is not
    // subject to const-correctness checks by the compiler.
    std::unique_ptr<int>  i2_;

public:
    void constFunc() const {
        i1_  = 123;
        *i2_ = 456;
    }
};

Несмотря на наличие этого дополнительного параметра, я по-прежнему согласен с πάντα ῥεῖ , что mutable ключевое слово - правильный выбор для таких случаев. Он явно документирует стандартным образом (например, позволяет быть grepped), что концептуальные операции const этого класса могут быть технически не мутирующими. Это полезно знать, например, когда речь идет о безопасности потоков функций const класса.

Ответ 3

signal:: connect изменяет signal:: slot. Итак, я думаю, что единственное, что вам нужно сделать, это изменить свой дизайн. Пусть signal:: connect может быть изменен, а вызывающие абоненты содержат указатели мутируемых сигналов.