Signal Slot Usage Over Abstract Class

Oguzhan
Dev Genius
Published in
3 min readMay 1, 2024

--

First of all i wanna show simple example of run time polymorphism that i used, then little bit mention about signal slot connection methods and lastly I will explain how to use signal slots over abstract class. Let’s delve deep into.

Code Example Of Run Time Polymorphism

// baseflow.h
#include <QObject>

class BaseFlow : public QObject
{
Q_OBJECT
public:
virtual ~BaseFlow() = default;
virtual void execute() = 0;
};
///////////////////////////////////////////////////////////////
// topupflow.h
#include <baseflow.h>
#include <QDebug>

class TopupFlow : public BaseFlow
{
Q_OBJECT
public:
void execute() override
{
qDebug() << Q_FUNC_INFO;
}

public slots:
void PaymentDone(int amount)
{
qDebug() << Q_FUNC_INFO << amount;
}
};
///////////////////////////////////////////////////////////////
// flowmanager.h
#include <QObject>

class FlowManager : public QObject
{
Q_OBJECT
public:
explicit FlowManager(QObject *parent = nullptr);
void run();
signals:
void PaymentDone(int amount);
};

// flowmanager.cpp
#include "flowmanager.h"
#include "baseflow.h"
#include "flowfactory.h"
#include "autoconnectman.h"

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

void FlowManager::run()
{
auto m_flow = FlowFactory::createFlow(TOPUP);
//AutoConnectMan::connect(this, m_flow.get()); //will toggle comment later
m_flow->execute();
//emit PaymentDone(100); //will toggle comment later
}
///////////////////////////////////////////////////////////////
// flowfactory.h
#include <memory>
#include <QtGlobal>

class BaseFlow;

enum FlowType : uchar {TOPUP = 1, CARD_SALE = 2};

class FlowFactory
{
public:
static std::unique_ptr<BaseFlow> createFlow(const FlowType txnType);
};

// flowfactory.cpp
#include "flowfactory.h"
#include "topupflow.h"

std::unique_ptr<BaseFlow> FlowFactory::createFlow(const FlowType txnType)
{
std::unique_ptr<BaseFlow> flow;
if (txnType == TOPUP)
flow = std::make_unique<TopupFlow>();
return flow;
}
///////////////////////////////////////////////////////////////
// main.cpp
#include "flowmanager.h"
#include <QDebug>

int main(int argc, char *argv[])
{
FlowManager flowMan;
flowMan.run();
}

The BaseFlow class is an abstract class, TopupFlow class overrides execute method for own purpose, FlowFactory class generates a flow, FlowManager class executes flows without knowing what class it uses.

What happens when run() method called, Firstly created Flow in run time(for simplicity used hard coded TOPUP type), then execute called.

Output

Signal & Slot Connection Methods

There are 3 ways that i ever seen mostly used to connect signals and slots ;

void someFunction();
QPushButton *button = new QPushButton;
///////////////////////// Using function pointers
QObject::connect(button, &QPushButton::clicked, this, &Class::someFunction);
///////////////////////// Using lambda expression
QObject::connect(button, &QPushButton::clicked, this, [](){
//someFunction implementation
});
///////////////////////// Using SIGNAL() SLOT() macros
QObject::connect(button, SIGNAL(clicked()), this, SLOT(someFunction()));

When used SIGNAL and SLOT macros in connect method, following overload called ;

static QMetaObject::Connection connect(const QObject *sender,
const char *signal,
const QObject *receiver,
const char *member,
Qt::ConnectionType = Qt::AutoConnection);

When SIGNAL(clicked()) executed in compile time, actually obtained “2clicked()” as a const char * and for SLOT(someFunction(), obtained “1someFunction()”.

So we can say macros add 1 or 2 in front of function signature.

we will do same thing with special functions to use signal & slots over abstract class, let’s add this class in our example and toggle commented lines in FlowManager::run().

// autoconnectman.h
#include <QMetaMethod>
#include <QObject>
#include <QDebug>

class AutoConnectMan
{
public:
static QList<QByteArray> scanType(QObject* object, QMetaMethod::MethodType type)
{
if (!object) return {};

auto metaObj = object->metaObject();
if (!metaObj) return {};

QList<QByteArray> list;
for (int i = metaObj->methodOffset(); i < metaObj->methodCount(); ++i) {
if (auto method = metaObj->method(i); method.methodType() == type)
list.append(method.methodSignature());
}
return list;
}
static void connect(QObject* sender, QObject* receiver)
{
if (!sender) return;
if (!receiver) return;

auto senderSignals = scanType(sender, QMetaMethod::Signal);
foreach (QByteArray sign, senderSignals) {

if (auto receiverSlots = scanType(receiver, QMetaMethod::Slot);
receiverSlots.contains(sign)) {

qDebug() << "auto connecting: " << sign;
QObject::connect(sender, "2"+sign, receiver, "1"+sign);
}
}
}
};

When AutoConnectMan::connect(this, m_flow.get()); called in run() method, it connects PaymentDone signal to slot if flow has it. Connection will end up when run() method life time end.

Output

Sources:

https://www.youtube.com/watch?v=spOalcPBUDI

https://doc.qt.io/qt-5/qobject.html

https://doc.qt.io/qt-5/qmetaobject.html

https://doc.qt.io/qt-5/signalsandslots.html

--

--