【问题标题】:Declare abstract signal in interface class在接口类中声明抽象信号
【发布时间】:2013-07-30 09:23:57
【问题描述】:

当实现类已经从 QObject/QWidget 派生时,如何在抽象类/接口中声明 Qt 信号?

class IEmitSomething
{
   public:
     // this should be the signal known to others
     virtual void someThingHappened() = 0;
}

class ImplementEmitterOfSomething : public QWidget, public IEmitSomething
{
     // signal implementation should be generated here
     signals: void someThingHappended();
}

【问题讨论】:

  • 您可以只写signals: void someThingHappened();。不需要实现信号。
  • 我知道信号是在实现中生成的,但是如果只知道接口,观察者怎么知道这是一个(qt)信号?
  • @Beachwalker 我已经在我的回答中用信号替换了受保护的关键字。它会悬停您的问题。
  • 你不能将信号声明取出到接口中,因为它使用QObject meta system,所以你的interface是有效的,你应该从QObject扩展IInterface并附加宏@987654327 @,但是多重继承(Qwidget和Qobject)调用编译报错
  • @Saz 这仅在 Qt 4 之前是正确的。在 Qt 5 中,信号是公开的 - 否则新的连接语法将不起作用。

标签: c++ qt qt-signals


【解决方案1】:

正如我在最后几天发现的...... Qt 这样做的方式是这样的:

class IEmitSomething
{
   public:
     virtual ~IEmitSomething(){} // do not forget this

   signals: // <- ignored by moc and only serves as documentation aid
            // The code will work exactly the same if signals: is absent.
     virtual void someThingHappened() = 0;
}

Q_DECLARE_INTERFACE(IEmitSomething, "IEmitSomething") // define this out of namespace scope

class ImplementEmitterOfSomething : public QWidget, public IEmitSomething
{
   Q_OBJECT
   Q_INTERFACES(IEmitSomething)

   signals:
      void someThingHappended();
}

现在您可以连接到这些接口信号了。

如果您在连接信号时无权访问实现,您的连接语句将需要动态转换为 QObject

IEmitSomething* es = ... // your implementation class

connect(dynamic_cast<QObject*>(es), SIGNAL(someThingHappended()), ...);

... 这样您就不必将实现类公开给订阅者和客户。是啊!!!

【讨论】:

  • 时间过去了...我不记得参考了,但如果你有关键字 Q_INTERFACES 和 Q_DECLARE_INTERFACE 那么你可以用谷歌搜索。我发帖时的问题是我不认识他们。你可以在这里阅读一些关于doc.qt.io/qt-5/qtplugin.html#Q_DECLARE_INTERFACE 的内容并浏览有关该界面的信息。
  • 知道为什么 virtual void someThingHappended();导致编译器警告,请参见此处:stackoverflow.com/questions/28614607/…
  • 可能是因为 Visual Studio 中 c++11 的变化(override 是一个新的关键字)
  • 根据this,信号不应该是虚拟的。
  • 我很惊讶这确实有效,但它需要旧式 SIGNAL()/SLOT() 语法这一事实让我感到很沮丧......
【解决方案2】:

在 Qt 中,“信号”是“受保护”的同义词。但它有助于 MOC 生成必要的代码。因此,如果您需要与某些信号的接口 - 您应该将它们声明为虚拟抽象受保护方法。所有必需的代码都将由 MOC 生成 - 您可能会看到详细信息,“发出一些信号”将被替换为具有相同名称的受保护方法的虚拟调用。注意,with 方法的主体也是由 Qt 生成的。

更新: 示例代码:

MyInterfaces.h

#pragma once

struct MyInterface1
{
signals:
    virtual void event1() = 0;
};

struct MyInterface2
{
signals:
    virtual void event2() = 0;
};

MyImpl.h

#ifndef MYIMPL_H
#define MYIMPL_H

#include <QObject>
#include "MyInterfaces.h"

class MyImpl
    : public QObject
    , public MyInterface1
    , public MyInterface2
{
    Q_OBJECT

public:
    MyImpl( QObject *parent );
    ~MyImpl();

    void doWork();

signals:
    void event1();
    void event2();
};

class MyListner
    : public QObject
{
    Q_OBJECT

public:
    MyListner( QObject *parent );
    ~MyListner();

public slots:
    void on1();
    void on2();
};

#endif // MYIMPL_H

MyImpl.cpp

#include "MyImpl.h"
#include <QDebug>

MyImpl::MyImpl(QObject *parent)
    : QObject(parent)
{}

MyImpl::~MyImpl()
{}

void MyImpl::doWork()
{
    emit event1();
    emit event2();
}

MyListner::MyListner( QObject *parent )
{}

MyListner::~MyListner()
{}

void MyListner::on1()
{
    qDebug() << "on1";
}

void MyListner::on2()
{
    qDebug() << "on2";
}

main.cpp

#include <QCoreApplication>
#include "MyImpl.h"

int main( int argc, char *argv[] )
{
    QCoreApplication a( argc, argv );

    MyImpl *invoker = new MyImpl( NULL );
    MyListner *listner = new MyListner( NULL );

    MyInterface1 *i1 = invoker;
    MyInterface2 *i2 = invoker;

    // i1, i2 - not QObjects, but we are sure, that they will be.
    QObject::connect( dynamic_cast< QObject * >( i1 ), SIGNAL( event1() ), listner, SLOT( on1() ) );
    QObject::connect( dynamic_cast< QObject * >( i2 ), SIGNAL( event2() ), listner, SLOT( on2() ) );

    invoker->doWork();

    return a.exec();
}

【讨论】:

  • 如果您确定 i1 是 QObject,则不需要 dynamic_cast。在这种情况下,static_cast 可以正常工作。
  • @FernandoPelliccioni 不,你完全错了。因为接口不继承QObject。只能使用reinterpret_cast,但它不起作用——因为存在多重继承。
  • @SaZ 哦!我误读了代码。我没有看到交叉铸造。在我的误读中,我想象了一个向下的铸造。抱歉打扰了。
【解决方案3】:

在接口中将信号声明为抽象方法有两个问题:

    1234563 /p>
  1. 直接从对象外部发出信号通常是糟糕的设计。

作为推论,由于接口是抽象的,因此您根本不需要声明它的信号 - 除了记录意图之外,它没有其他用途,因为:

  1. 如果信号在派生自接口的类中实现,则可以使用元对象系统来验证它的存在。

  2. 无论如何,您不应该直接调用这些信号方法。

  3. 将非对象接口动态转换为 QObject 后,从接口派生的实现就不再重要了。

做这种体操的唯一正当理由是:

  1. 诱使 doxygen 或其他文档生成器为您的代码提供文档。

  2. 强制具体类具有同名方法的实现。这当然不能保证它实际上是一个信号。

【讨论】:

  • 我觉得在抽象接口中声明信号还有另一个原因:假设您有一些小部件,它通过指向该接口的指针来接受对象。让对象具有抽象方法QString getName()setName(QString) 和信号nameModified。然后,您的小部件可以在构造函数中订阅此信号,以便通过具体实现获得有关名称更改的通知。
  • 这对于按名称建立的连接(QT4 语法)可以正常工作,并且基类甚至不需要有信号,只要派生类有。相反,基类需要的是一个virtual QObject *object() = 0; 方法(所有这些“从接口派生QObject”类都需要它!)——否则你将无法获得指向对象的指针:)跨度>
  • 还有一个原因:如果你是从抽象接口派生的,那就意味着你在使用多重继承(因为你必须派生 drom QObject 才能使用信号,而抽象接口不是从 QObject 派生的)。多重继承阻止您使用 Qt5 形式的 connect()
  • (因为在 C++ 中比较指向虚成员函数的指针是未指定的,并且 QMetaObject 找不到信号。)
【解决方案4】:

我们都希望永远摆脱 MOC,但在此之前,我想添加一个替代方案,它可以在不包含 QObject.h 且不使用接口类中的 Q_OBJECT 和 Q_INTERFACE 的情况下工作。

首先在接口中定义一个抽象的connect函数:

class I_Foo
{
public:
    virtual void connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) = 0;
};

现在在派生类中,重写该函数。还要声明信号,添加 Q_OBJECT 等。

class Bar : public QObject, public I_Foo
{
    Q_OBJECT

public:
    void connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);

signals:
    void A();
};

然后在类 .cpp 中进行连接:

Bar::connectToSignalA(const QObject * receiver, const char *method, Qt::ConnectionType void type)
{
    connect(this, SIGNAL(A()), receiver, method, type);
}

需要注意的是,您必须在每个派生类中编写连接函数,并且必须使用旧式连接(或者可能使用模板函数),但仅此而已。

【讨论】:

    猜你喜欢
    • 2013-12-30
    • 2015-12-28
    • 2021-11-22
    • 2016-05-10
    • 2014-02-26
    • 1970-01-01
    • 2013-05-22
    • 1970-01-01
    • 2019-01-05
    相关资源
    最近更新 更多