为什么Qt信号不是const?

22

Qt使用信号和槽机制进行对象通信。通常将信号声明为成员函数,然后由Qt MOC生成该函数的定义。

我想了解的是为什么信号不是const成员函数?

编辑: 我期望信号不会修改发送者,这就是我的问题所在。


2
你认为它们为什么应该是“const”? - Captain Obvlious
1
@CaptainObvlious:他们不会改变发送实例,对吧? - Ben Voigt
@BenVoigt 我不知道,所以我才问。 - Captain Obvlious
@selbie 只是想了解为什么信号默认情况下不是const。 - Mac
2
@Mike:这没有问题,连接将包含一个非const指向接收器的指针,这并不重要,即使this指针是const限定的。 - Ben Voigt
显示剩余6条评论
3个回答

17

我希望信号不会修改发送者

信号(如MOC生成的)不直接修改类实例的成员。然而,生成的代码会传递一个this指针,以供(可能的)槽函数使用。连接的槽函数因此可以改变信号的发送者。

所以技术上的原因是,如果信号是const的,那么所有的槽函数实现都必须只调用发送者的const类成员,才能使代码编译通过而没有错误。

将信号实现为非const类成员是一个可以理解的决定,考虑到代码安全性。但在某些情况下仍然感觉不自然(例如,如果连接的槽函数在同一类中实现为const,或者连接的槽函数属于完全不同的对象)。


3
“it would require that all slot implementations would also have to be const” 的意思是不需要,它意味着接收指向发送者对象的指针参数将会被添加常量限定符,而不是隐式的“this”参数。 - Ben Voigt
1
@BenVoigt:你是对的。这是基于一个假设写的,即信号和槽在同一个类中实现。这并非必须的,事实上通常并不是这种情况。 - IInspectable
即使在同一个类中实现,我担心你的观点是错误的,你尝试过吗?如果是这样,使用哪个Qt版本?请参见我在答案中所做的测试。 - ymoreau
@ymoreau: 我不记得我看过哪个Qt版本。那时可能是4.x,因为我一直在使用。无论如何,你的测试展示了未定义的行为。除了开发人员(或工具)能够使编译器静音之外,它并没有证明任何东西。根据C ++语言规范,你的测试应用程序没有被很好地定义。 - IInspectable
@IInspectable const_cast并不总是未定义的行为,否则它为什么存在?只有当您将指向的对象声明为const并且还修改其内存时,它才是未定义的行为,但是没有人声明const QObject obj;所以这是无关紧要的。使用const信号的唯一缺点是在sender()上失去const正确性,这通常甚至都不被使用。即使在使用时,调用非const函数也不是无效的,只是很糟糕的做法而已。 - Joseph Ireland
显示剩余2条评论

11

据我所知,没有任何东西阻止Qt信号成为const(在Qt5.9中测试过)。IInspectable的答案是不正确的。

下面是我进行的测试,以表明仍然可以:
- 将const信号连接到同一实例中的非const槽
- 在sender()上调用非const方法

我的测试类编译良好(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;
};

这里是MOC为信号生成的代码:

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

我们可以看到,Qt在this上使用了const_cast,所以一切都能正常工作。
我认为信号默认不是const的原因是这将要求MOC在你的头文件中添加一个const到你的信号(==类方法)定义中,从而修改你的源代码。
我猜这可能通过将每个信号定义封装在宏中来实现,但想象一下对于编码者和读者来说会有多么痛苦。我没有看到Qt(也不是你)在你的信号声明为const时有任何收益,这将需要更多的工作量。
但是,有时您可能需要将它们声明为const,例如当您想从const方法中发出它们时。而您可以自由地这样做。

2
使用 const_cast 移除 const 限定符是可以的。但是在移除限定符后通过指向 const 的指针/引用修改对象是未定义行为。看起来正常运作只是未定义行为的一种形式。它仍然是未定义的,符合规范的实现不会对结果或整个程序的完整性做任何保证。 - IInspectable
谢谢您的反馈,您有关于这些未定义行为案例的任何资源吗?我知道它可能是静态数据的问题,但不太清楚当涉及到对象指针时为什么会出现问题。特别是const_cast是由MOC生成的,然后QMetaObject::activate明显使用该指针调用非const方法,例如:sender->d_func()。我假设Qt开发人员在生成此const_cast时知道他们在做什么,对吗? - ymoreau
1
请参阅[expr.const.cast][dcl.type.cv]。这不是关于你认为它如何工作的问题。C++是根据抽象机器定义的。留下某些构造未定义允许编译器做出假设,从而打开某些优化可能性。 "我假设Qt开发人员知道他们在做什么" - 我没有理由相信这一点。 - IInspectable
1
据我所知,只要原始对象没有声明为const,修改它是安全的。因此,我同意在这里使用const_cast并不是100%安全的,但是当您使用Qt时,您知道在QObject上这样做是极不鼓励的。 - ymoreau

3

信号可以是const的,实际上如果你想从一个const函数中发出信号,那么这个信号必须是const的,但在槽函数中它不需要是const的。当使用const pures重新实现抽象类时,我通过这种方式避免了使用可变对象。


“我已经避免使用可变对象” - 这只是在Qt的机制后面隐藏了一个const_cast。如果这是对自身的QueuedConnection,则可能是安全的。否则(直接到自身),请预期未定义的行为。 - Patrick Parker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接