如何在不继承自QObject的情况下使用SIGNAL和SLOT?

19

还有其他的方式来表达我的问题(虽然它没有解决我的问题):'QObject::QObject' cannot access private member declared in class 'QObject'

我需要在我的类中使用SIGNALs和SLOTS功能,但我认为如果不从QObject派生是不可能实现的?

class MyClass
{
signals:
   importantSignal();
public slots:
   importantSlot();
};
问题似乎是我需要继承自QObject来使用信号和槽,但我需要MyClass的默认构造函数。但由于QObject的以下特性,我无法构建它们: 没有复制构造函数或赋值运算符
我尝试了很多方法...
所以我的shoul类应该像这样:
#include <QObject>
class MyClass: public QObject
{
    Q_OBJECT
public:
    explicit MyClass(QObject *parent = 0); //autogenerated by qtcreator for QObject derived class
    MyClass(const MyClass * other);

signals:
    importantSignal();
public slots:
    importantSlot();
};

我需要MyClass的默认构造函数。

那么有没有可能避免 "'QObject:: QObject' 无法访问在类 'QObject' 中声明的私有成员" 错误?

或者,有没有可能在没有使用QObject的情况下使用信号和槽?

我很高兴听取任何建议。


5
如果您查看Qt QObject的文档,它们会谈论为什么不应该将QObjects视为“值”(没有复制构造函数),而应始终使用指针引用它们,换句话说,将它们作为独特的对象。也许我们可以稍微改变您的设计,以便您可以利用信号/槽。例如,如果您想在容器/列表中存储类的实例,可以存储指针而不是对象本身。您确切地需要默认构造函数吗?或者,您如何使用这个类? - Liz
我使用这个类作为数据持有者,而不是一个结构体...每个实体作为一个元组。我尝试派生QObject的原因是我想要使用信号和槽来加载Web内容(图片)... - 6e69636b6e616d65
我还想在自己编写的容器/集群中存储实例。我需要默认构造函数来获取空实例。 - 6e69636b6e616d65
我使用我的类来存储值,例如从网络加载的图片。例如,这张图片会在地图上绘制(使用MarbleWidget)。 - 6e69636b6e616d65
6个回答

12
如果你想要一个带有QObject特性的可复制对象,你需要使用成员(通过指针)而不是继承。
你可以从QObject派生一个类Handler,在Handler的槽函数中调用其父对象的SomeInterface虚函数。
struct NonQObjectHandler {
    virtual ~ NonQObjectHandler () {}
    virtual void receive (int, float) = 0;
};

class Handler : public NonQObjectHandler {
    struct Receiver;
    std :: unique_ptr <Receiver> m_receiver;
    void receive (int, float); // NonQObjectHandler
public:
    Handler ();
    Handler (const Handler &); // This is what you're really after
};

class Handler :: Receiver : public QObject {
Q_OBJECT
private:
    NonQObjectHandler * m_handler;
private slots:
    void receive (int, float); // Calls m_handler's receive
public:
    Receiver (NonQObjectHandler *);
};

Handler :: Handler ()
: m_receiver (new Receiver (this))
{
}

Handler :: Handler (const Handler & old)
: m_receiver (new Receiver (this))
{
    // Copy over any extra state variables also, but
    // Receiver is created anew.
}

Handler :: Receiver :: Receiver (NonQObjectHandler * h)
: m_handler (h)
{
    connect (foo, SIGNAL (bar (int, float)), this, SLOT (receive (int, float)));
}

void Handler :: Receiver :: receive (int i, float f)
{
    m_handler -> receive (i, f);
}

1
您可以给出一个示例/片段吗? - 6e69636b6e616d65

7
如果您想使用信号/槽模式实现事件驱动功能,但不想在Qt的限制内工作(即,您想要在需要复制构造函数的STL容器中使用您的类等),我建议使用Boost::signal
否则,没有办法不从QObject派生,因为这个基类处理Qt运行时的信号/槽功能。

1
嗯,好的建议,但我喜欢使用Qt。现在换用另一个库有点晚了,但还是谢谢。 - 6e69636b6e616d65
有没有办法避免 "'QObject::QObject' cannot access private member declared in class 'QObject'" 这个错误? - 6e69636b6e616d65
1
是的... 不要做需要复制构造函数或赋值运算符的事情。这基本上意味着,与可能需要复制以移动的实例相比,通过指针使用从 QObject 派生的对象通常是一个很好的选择。这并不是说你不能使用实例,但你不能复制它们,因此你也不能把它们放在 STL 容器中等等。 - Jason

2
自从Qt5以后,你可以连接到任何函数。
connect(&timer, &QTimer::finished,
        &instanceOfMyClass, &MyClass::fancyMemberFunction);

MyClass不继承自QObject时,这个代码还能正常工作吗? - scopchanov
不过,你可以省略接收器(第三个参数),并使用静态函数或std::bind / lambdas将对象和成员函数绑定到可调用对象上。 - ManuelSchneid3r

2

如果不使用 QObject/Q_OBJECT,就无法使用Qt的信号/槽机制。

理论上,您可以创建一个虚拟的QObject并将其组合到您的类中。然后,虚拟的QObject将转发槽调用到您的类。但是,由于Liz在评论中所述的原因,您可能会遇到生命周期管理方面的问题。


你可以在没有 Q_OBJECT 的情况下使用 connect()。但是如果没有 Q_OBJECT,你将无法使用 emit。 - Ph0t0n

1
在Qt5中,您可以使用QObject::connect来连接signalslot
/*
   QMetaObject::Connection QObject::connect(
    const QObject *sender,
    const char *signal,
    const char *method,
    Qt::ConnectionType type = Qt::AutoConnection) const;
 */

#include <QApplication>
#include <QDebug>
#include <QLineEdit>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QLineEdit lnedit;

    // connect signal `QLineEdit::textChanged` with slot `lambda function`
    QObject::connect(&lnedit, &QLineEdit::textChanged, [&](){qDebug()<<lnedit.text()<<endl;});

    lnedit.show();
    return app.exec();
}

结果:

enter image description here


0
仅仅因为 QObject 不可复制,并不意味着当你的类被复制或分配时就必须要复制它。具体来说,你需要做的就是保护你的类免受 QObject 的复制和赋值操作(因为它们被删除了)。
通常,你会将“可复制但不可复制 QObject”提取到单独的一个类中:
// main.cpp
#include <QObject>
#include <QVariant>

class CopyableQObject : public QObject
{
protected:
   explicit CopyableQObject(QObject* parent = nullptr) : QObject(parent) {}
   CopyableQObject(const CopyableQObject& other) { initFrom(other); }
   CopyableQObject(CopyableQObject&& other)      { initFrom(other); }
   CopyableQObject& operator=(const CopyableQObject& other)
   {
      return initFrom(other), *this;
   }
   CopyableQObject& operator=(CopyableQObject&& other)
   {
      return initFrom(other), *this;
   }
private:
   void initFrom(const CopyableQObject& other)
   {
      setParent(other.parent());
      setObjectName(other.objectName());
   }
   void initFrom(CopyableQObject& other)
   {
      initFrom(const_cast<const CopyableQObject&>(other));
      for (QObject* child : other.children())
         child->setParent( this );
      for (auto& name : other.dynamicPropertyNames())
         setProperty(name, other.property(name));
   }
};

你可以在 QObject 实例之间复制任何所需的属性。以上复制了父对象和对象名称。此外,当从另一个对象移动时,其子项将被重新分配父级,并且动态属性名称也将被传输。
现在,CopyableQObject 适配器实现了所有构造函数,允许派生类具有可复制、可复制赋值、可移动构造和可移动赋值。
您的类只需要从上述适配器类派生即可。
class MyClass : public CopyableQObject
{
   Q_OBJECT
public:
   Q_SIGNAL void signal1();
   Q_SIGNAL void signal2();
};

我们可以测试它是否有效:
int main()
{
   int counter = 0;

   MyClass obj1;
   MyClass obj2;
   Q_SET_OBJECT_NAME(obj1);

   QObject::connect(&obj1, &MyClass::signal1, [&]{ counter += 0x1; });
   QObject::connect(&obj1, &MyClass::signal2, [&]{ counter += 0x10; });
   QObject::connect(&obj2, &MyClass::signal1, [&]{ counter += 0x100; });
   QObject::connect(&obj2, &MyClass::signal2, [&]{ counter += 0x1000; });

   Q_ASSERT(counter == 0);
   emit obj1.signal1();
   emit obj1.signal2();
   Q_ASSERT(counter == 0x11);

   QObject obj3(&obj1);
   Q_ASSERT(obj3.parent() == &obj1);
   const auto name1 = obj1.objectName();

   obj2 = std::move(obj1);

   Q_ASSERT(obj3.parent() == &obj2);
   Q_ASSERT(obj2.objectName() == name1);

   emit obj2.signal1();
   emit obj2.signal2();
   Q_ASSERT(counter == 0x1111);
}

#include "main.moc"

这就是完整的、可编译的示例。


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