13

I'm implementing my very own signal/slot (observer pattern, Qt style) mechanism so I can have a property that notifies ... stuff... that is has changed.

I think C++11 provides everything necessary to make a very succint and featureful implementation possible. The "issue" I'm running into is if I want to "connect" to a signal of a const object, I need the signal::connect function to be const, but modify the list of callbacks/observers. There are two straightforward ways to fix this:

  1. const_cast the lists inside connect.
  2. Make the lists mutable.

Both seem to me like the same thing (and this has been asked before, e.g. in this question), and perfectly fine logically, but stylistically questionable. Hence the question. Is there a way around this or is this a truly justified use of const_cast/mutable?

Some prelimenary code as I have it now:

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;

};

Note I haven't tested this code; this is just a reflection of my current state of mind.

Community
  • 1
  • 1
rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • 2
    Non-tested code... tsk tsk... – Arnav Borborah Aug 27 '16 at 15:03
  • @Arnav I'm literally writing the tests now, I just needed to get this design issue out of the way :p. – rubenvb Aug 27 '16 at 15:49
  • I am afraid I do not understand why the `signal` itself should expose `const` methods. Why not let the user of `signal` decide whether they want it to be mutable (or not)? – Matthieu M. Aug 27 '16 at 21:22
  • @MatthieM. Imagine the signal as a member of an object. If that object is const in a certain context, you cannot simply connect to a signal thereof, which necessitates user code to give up a level of const-correctness. I that's not clear, I can try to provide a code sample of what I mean. – rubenvb Aug 27 '16 at 21:40

3 Answers3

10

mutable is usually the better choice for such cases.

Avoid (const) casting whenever you can, it's prone for hitting undefined behavior, while mutable is guaranteed to be not1).


1 mutable class members are guaranteed not to go to emitted codes .text segment for instance.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
3

There are two straightforward ways to fix this:

  1. const_cast the lists inside connect.
  2. Make the lists mutable.

In fact there is a third choice (which is a general purpose would-be workaround, had C++ not provided the mutable keyword) - you can move the concerned data out of the syntactical object:

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;
    }
};

Despite the availability of this additional option, I still concur with πάντα ῥεῖ's answer that the mutable keyword is the right choice for such cases. It explicitly documents in a standardized way (e.g. allows to be grepped) that the conceptually const operations of this class may not be technically non-mutating. This is good to know, for example, when being concerned with thread safety of const functions of the class.

Community
  • 1
  • 1
Leon
  • 31,443
  • 4
  • 72
  • 97
0

The signal::connect does modify signal::slot. So, I think the only thing you need to do is change your design. Let signal::connect be mutable and callers hold mutable signal pointers.

kitt
  • 582
  • 3
  • 9