Реализация сигналов (шаблон Observer): необходим ли mutable или const_cast?

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

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

  1. const_cast списки внутри connect.
  2. Составьте списки 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's signals
  mutable std::vector<std::function<void(ArgTypes...)>> slots;
  std::mutex slots_mutex;

};

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


person rubenvb    schedule 27.08.2016    source источник
comment
Непроверенный код... тск тск...   -  person Arnav Borborah    schedule 27.08.2016
comment
@Arnav Я буквально сейчас пишу тесты, мне просто нужно было решить эту проблему с дизайном: с.   -  person rubenvb    schedule 27.08.2016
comment
Боюсь, я не понимаю, почему сам signal должен выставлять методы const. Почему бы не позволить пользователю signal решить, хотят ли они, чтобы он был изменчивым (или нет)?   -  person Matthieu M.    schedule 28.08.2016
comment
@МэттиМ. Представьте сигнал как член объекта. Если этот объект является константным в определенном контексте, вы не можете просто подключиться к его сигналу, что требует от пользовательского кода отказа от уровня константной корректности. Я не понимаю, я могу попытаться предоставить пример кода того, что я имею в виду.   -  person rubenvb    schedule 28.08.2016


Ответы (3)


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

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


1mutable члены класса гарантированно не перейдут, например, к .text сегменту испускаемых кодов.

person πάντα ῥεῖ    schedule 27.08.2016

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

  1. const_cast списки внутри connect.
  2. Составьте списки 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 является правильным выбором для таких случаев. Он явным образом документирует стандартизированным способом (например, позволяет выполнить поиск), что концептуально const операции этого класса могут не быть технически немутирующими. Это полезно знать, например, когда речь идет о безопасности потоков const функций класса.

person Leon    schedule 27.08.2016

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

person kitt    schedule 28.08.2016