Is it valid to define a pure virtual signal in C++

2019-09-10 08:36发布

问题:

I am making an abstract-base-class and was thinking I might want a pure virtual signal. But when I compiled I get a warning for the pure virtual signals I have defined:

../FILE1.h:27: Warning: Signals cannot be declared virtual
../FILE1.h:28: Warning: Signals cannot be declared virtual

Is it valid to define a pure virtual signal in C++/Qt? Is it valid to define a virtual signal?

Qt's signal and slot documentation page says you can define virtual slots but doesn't talk about signals. I can't seem to find good information on pure virtual signals.

回答1:

  • Signals don't ever have an implementation[1] (i.e. you define the signal in your .h file and then there is no implementation in the .cpp).
  • The main purpose of declaring a function pure virtual is to force the inheriting class to provide an implementation.

Given the above two statements here's my thinking:

Signals don't have an implementation but declaring it pure virtual will require the inheriting class to provide an implementation... which directly conflict with "signals don't have an implementation". It's like asking someone to be in two places at once it's just not possible.

So in conclusion it seems like declaring a "pure virtual" "signal" should be an error and thus not valid.


In the case of an abstract base class here's what I think is correct:

When one declares the function only "virtual" it still gives the warning. To avoid any warnings I think the solution is to not qualify the signal with any "virtual" or "pure virtual" and then the inheriting class will not declare any signals but can still emit the signals defined in the base class.

[1] when I say that "signals don't ever have an implementation" I mean that the person implementing the class doesn't provide the implementation. I understand that behind the scene Qt's moc provides an implementation in the moc_FILE1.cpp .



回答2:

The warning is reported by moc, not by the C++ compiler, and it is valid except in the specific case of abstract interfaces.

The only valid use for virtual signals is when declaring abstract interfaces that don't derive from QObject, as detailed in this excellent answer. There's nothing wrong with that approach. Moc tries to be helpful, since in most cases a virtual signal is a mistake.

Even then, the simple workaround for not getting the warning is to skip the signals: keyword in the interface. It is completely unnecessary, since the interface doesn't derive from QObject and thus shouldn't be processed by moc at all:

// https://github.com/KubaO/stackoverflown/tree/master/questions/virtual-slot-10029130
#include <QtCore>

class IDogInterface {
public:
   // no signals: section since it's not a QObject!
   virtual void barks() = 0; // a signal
};

class ADog : public QObject, public IDogInterface {
   Q_OBJECT
public:
   Q_SIGNAL void barks() override; // implementation is generated by moc
};

class Monitor : public QObject {
   Q_OBJECT
   int m_count{};
   Q_SLOT void onBark() { m_count++; }
public:
   int count() const { return m_count; }
   void monitorBarks(IDogInterface * dog) {
      QObject * dogObject = dynamic_cast<QObject*>(dog);
      if (dogObject) {
         connect(dogObject, SIGNAL(barks()), SLOT(onBark()));
      } else {
         qWarning() << "cannot monitor barking on dog instance" << (void*)dog;
      }
   }
};

int main() {
   ADog dog;
   Monitor monitor;
   monitor.monitorBarks(&dog);
   emit dog.barks();
   Q_ASSERT(monitor.count() == 1);
}
#include "main.moc"


回答3:

I think there's simply no point in having (pure) virtual signals. the signals macro provided to Qt simply expands to protected, so all the signals you're declaring are actually declarations of protected methods. The code generated by moc will provide the implementations of those functions.



回答4:

there is a solution to create pure virtual function which will connect given slot to signal or vice versa. e.g.:

class IBaseInterface
{
  public:
    virtual bool connectToSignal1(QObject* pReceiver, const char* pszSlot, bool bConnect) const = 0;
};

class CDerived : public QObject, public IBaseInterface
{
  Q_OBJECT
  public:
    virtual bool connectToSignal1(QObject* pReceiver, const char* pszSlot, bool bConnect) const;
  signals:
    void signal1(const QString& msg);
};

bool CDerived::connectToSignal1(QObject* pReceiver, const char* pszSlot, bool bConnect) const
{
 if(bConnect)
   return connect(this, SIGNAL(signal1(QString)), pReciever, pszSlot);
 return disconnect(this, SIGNAL(signal1(QString)), pReciever, pszSlot);
}

further in clients code one may type:

class CSomeClass : public QObject
{
    Q_OBJECT
protected /*or public, or private*/ slots:
    void someSlot(const QString& msg);
};
void CSomeClass::somefunction()
{
    IBaseInterface* p = new CDerived;
    if (!p->connectToSignal1(this, SLOT(someSlot(QString)), true))
    QMessageBox::warning(this, tr("Warning"), tr("Cannot connect ...."), QMessageBox::Ok);
}


回答5:

Two scenarios where a virtual signal makes sense:

  1. The derived class may want to selectively block the sending of a signal by skipping the base class implementation.
  2. The derived class may want to use it as an event mechanism and react to the signal before or after sending it to listeners.

Both scenarios can also be handled in other less-OOP ways.