Why are Qt signals NOT const
Asked Answered
C

3

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.

Counterbalance answered 1/9, 2016 at 22:21 Comment(11)
Why do you think they should be const?Lo
@CaptainObvlious: They don't change the sending instance, do they?Gigolo
@BenVoigt I don't know, that's why I'm asking.Lo
@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.Counterbalance
Mac - what are you really trying to do?Grivation
@Grivation just trying to understand why signals are not const by default.Counterbalance
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. . .Apicella
@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.Gigolo
@BenVoigt , you are right, I am sorry I missed that point. . .Apicella
Actually a good question, I still cannot see why not. Some details on the matter : woboq.com/blog/how-qt-signals-slots-work.htmlDomesticate
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 ?) : stackoverflow.com/questions/5781449Domesticate
O
18

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).

Ohmmeter answered 2/9, 2016 at 0:0 Comment(7)
"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..Gigolo
@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.Ohmmeter
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.Domesticate
@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.Ohmmeter
@Ohmmeter 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.Fruiterer
@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.Ohmmeter
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.Gautea
D
14

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.

Domesticate answered 26/10, 2017 at 12:9 Comment(4)
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.Ohmmeter
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 ?Domesticate
See [expr.const.cast] and [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.Ohmmeter
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.Domesticate
A
3

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.

Apicella answered 4/10, 2018 at 19:42 Comment(1)
"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.Weird

© 2022 - 2024 — McMap. All rights reserved.