22

Qt uses signals and slots for object communication. Signals are normally declared as a member function and the Qt MOC then generates the definition of that function.

What I would like to understand is why signals are not const member functions?

Edit: I would expect signals not to modify the sender, that's why the question.

ymoreau
  • 3,402
  • 1
  • 22
  • 60
Mac
  • 3,397
  • 3
  • 33
  • 58
  • 2
    Why do you think they should be `const`? – Captain Obvlious Sep 01 '16 at 22:30
  • 1
    @CaptainObvlious: They don't change the sending instance, do they? – Ben Voigt Sep 01 '16 at 22:32
  • @BenVoigt I don't know, that's why I'm asking. – Captain Obvlious Sep 01 '16 at 22:35
  • @CaptainObvlious I would expect the "sender" not to be modified, but since the implementation is done by the moc. I don't really know what it does. – Mac Sep 01 '16 at 22:48
  • Mac - *what are you really trying to do*? – selbie Sep 01 '16 at 23:17
  • @selbie just trying to understand why signals are not const by default. – Mac Sep 01 '16 at 23:18
  • and what if the signal is connected to some *non-const* slot that belongs to the same object? the implementation generated by the *MOC* would have to call that *non-const* slot from a const function. . . – Mike Sep 01 '16 at 23:44
  • 2
    @Mike: There's no problem with that, the connection will contain a non-const pointer to the receiver, it doesn't matter if the `this` pointer is `const`-qualified. – Ben Voigt Sep 02 '16 at 14:26
  • @BenVoigt , you are right, I am sorry I missed that point. . . – Mike Sep 02 '16 at 14:31
  • Actually a good question, I still cannot see why not. Some details on the matter : https://woboq.com/blog/how-qt-signals-slots-work.html – ymoreau Oct 26 '17 at 10:22
  • Interesting point raised in another question, you can explicitly declare your signals as const, so maybe it is just to allow the coder to choose that the moc does not generate const by default (you would end up with `mysignal() const const` right ?) : https://stackoverflow.com/questions/5781449 – ymoreau Oct 26 '17 at 10:27

3 Answers3

17

I would expect signals not to modify the sender

Signals (as generated by the MOC) do not directly modify a class instance' members. The generated code, however, passes a this pointer along, for consumption by a (potential) slot. A connected slot could thus mutate the sender of the signal.

So the technical reason is, that if signals were const, it would require that all slot implementations would only call const class members on the sender for the code to compile without errors.

Implementing signals as non-const class members is an understandable decision, with respect to code safety. It still feels unnatural in a number of cases (e.g. if the connected slot implemented in the same class is const, or if the connected slot belongs to another object altogether).

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • 3
    "it would require that all slot implementations would also have to be const" -- no, it would mean that the parameter receiving a pointer to the sender object would be `const`-qualified, not implicit `this` argument.. – Ben Voigt Sep 02 '16 at 14:28
  • 1
    @BenVoigt: You are right. That was written under the assumption, that signals and slots are implemented in the same class. This is not required, and in fact often not the case. – IInspectable Sep 02 '16 at 15:30
  • Even if implemented in the same class, I'm afraid your point is wrong, have you tried this ? If so, using which Qt version ? See the test I made in my answer. – ymoreau Oct 26 '17 at 12:11
  • @ymoreau: I do not recall, which Qt version I looked into. It was probably 4.x, which I have been using at the time. Regardless, your test exhibits undefined behavior. It doesn't prove anything other than a developer's (or tool's) ability to silence the compiler. Your test application is not well defined according to the C++ Language Specification. – IInspectable Oct 26 '17 at 13:56
  • @IInspectable const_cast isn't always undefined behaviour, otherwise why would it exist? It's only undefined behaviour if you declare the pointed-to object as const and also modify its memory, but no one ever declares a `const QObject obj;` so it's irrelevant. The only downside to a const signal is losing const correctness on `sender()`, which often isn't even used. And even when it is used, it's not invalid to call non-const functions, it's just bad practice. – Joseph Ireland Jul 11 '19 at 09:30
  • @jos: A `const_cast` is well defined in case the `const`-qualified pointer/reference points to an object that isn't `const`-qualified. So that's not a problem. Once you do subscribe to a `const_cast`, you are giving up your last chance for the compiler to inform you about undesired code constructs. Software systems do change over time, and what used to be well defined code at one point in time could turn into undefined behavior by a change in largely unrelated code. And there's no tool to let you know anymore, because it was deliberately silenced. – IInspectable Jul 11 '19 at 10:20
  • A signal is used to inform other components that an object's state has changed, which inherently implies that the object's state has been modified. Declaring a signal as const contradicts its purpose, as const is used to indicate that a member function does not modify the object's state. – Rob Mar 30 '23 at 15:56
11

Nothing prevents a Qt signal to be const AFAIK (tested with Qt5.9). The answer from IInspectable is not correct.

Below is a test I made to show it is still possible to
- connect a const signal to a non-const slot in the same instance
- call non-const methods on the sender().

The class of my test, which compiles fine (gcc) :

// Stupid class to test the emit of a const-signal
class Test : public QObject
{
    Q_OBJECT

public:
    // Connect in the constructor from this instance to this instance
    explicit Test(QObject *parent = nullptr) : QObject(parent) {
        connect(this, &Test::valueChanged, this, &Test::updateString);
    }

    // To test a non-const method call
    void nonConstFoo() {
        setObjectName("foo"); // I modify the 'this' instance
    }

    // To test emit of a non-const signal from a const-method
//    void constFoo() const {
//        emit fooSignal(); // --> FAIL at compile time
//    }

public slots:
    void setValue(int value) {
        m_value = value;
        emit valueChanged(value);
    }

    void updateString(int value) {
        m_string = QString::number(value); // I modify the 'this' instance
        nonConstFoo(); // I modify the 'this' instance through a non-const call

        auto s = sender();
        s->setObjectName("mutated name"); // I modify the 'sender' instance

        qDebug() << "Updated string" << m_string;
    }

signals:
    void valueChanged(int) const; // The signal is const
    void fooSignal(); // Non-const signal

private:
    int m_value;
    QString m_string;
};

And here is the code generated by the MOC for the signals :

// SIGNAL 0
void Test::valueChanged(int _t1)const
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(const_cast< Test *>(this), &staticMetaObject, 0, _a);
}

// SIGNAL 1
void Test::fooSignal()
{
    QMetaObject::activate(this, &staticMetaObject, 1, nullptr);
}

We can see that Qt uses const_cast on this so everything will work anyway.


In my opinion, the reason the signals are not const by default is that this would require the MOC to add a const to your signal (== class-method) definition in your header, therefore modify your source code.

I guess this would be doable by enclosing each definition of signal in a macro, but imagine the pain for the coder and for the reader. I do not see any gain for Qt (nor you) that you have your signals declared as const, and this would require more work for you and Qt.

But you may need to declare them as const sometimes. Like when you want to emit them from a const method. And you are free to do so.

ymoreau
  • 3,402
  • 1
  • 22
  • 60
  • 2
    Casting away a `const` qualifier using a `const_cast` is fine. Mutating an object through a pointer/reference to `const` after casting away the qualifier is undefined behavior. Appearing to work is a perfectly valid form of undefined behavior. It's still undefined, and a conforming implementation makes **no** guarantees about the outcome, or the entirety of the program. – IInspectable Oct 26 '17 at 13:33
  • Thx for the feedback, do you have any resource about these cases of undefined behaviour ? I see how it can be a problem on static data, but not really why when it comes to an Object-pointer. Especially that the `const_cast` is generated by MOC and then `QMetaObject::activate` is clearly using that pointer to call non-const methods like : `sender->d_func()`. I assume Qt-devs know what they are doing when generating this `const_cast` right ? – ymoreau Oct 26 '17 at 15:00
  • 1
    See [\[expr.const.cast\]](http://eel.is/c++draft/expr.const.cast) and [\[dcl.type.cv\]](http://eel.is/c++draft/dcl.type.cv). This is not about how you *think* this works. C++ is defined in terms of an abstract machine. Leaving certain constructs undefined allows a compiler to make assumptions, opening up certain optimization possibilities. *"I assume Qt-devs know what they are doing"* - I have no reason to believe that. – IInspectable Oct 26 '17 at 15:25
  • 1
    From what I understand, as long as the original object is **not** declared as const it is safe to modify it. So I agree the `const_cast` is not 100% safe here, but when you use Qt you know it is highly discouraged to do such a thing on a QObject. – ymoreau Oct 27 '17 at 08:00
2

Signals can be const, in fact if you want to emit a signal from a const function it must be a const signal, but on the slot end it doesn't need to be const. I've gotten out of having to use mutables this way when reimplementing abstract classes with const pures.

Mike
  • 190
  • 7
  • "I've gotten out of having to use mutables" -- that is just hiding a const_cast behind Qt's machinery. If this is a QueuedConnection to self, then it may be safe. Otherwise (Direct to self) expect undefined behavior. – Patrick Parker Mar 07 '22 at 20:32