Qt Overview
{:.gc-basic}
Basic
Qt is a cross-platform C++ framework used in embedded Linux HMIs, desktop applications, and industrial control systems. It extends C++ with a meta-object system that enables signals & slots, reflection, and QML.
Key modules:
| Module | Purpose |
|---|---|
QtCore |
QObject, signals/slots, event loop, threads, containers |
QtWidgets |
Desktop GUI widgets (QPushButton, QLabel, QMainWindow) |
QtQuick/QML |
Fluid, declarative UI for touch/embedded |
QtSerialPort |
UART/serial communication |
QtNetwork |
TCP/UDP sockets, HTTP |
QtDBus |
IPC via D-Bus (Linux) |
Hello Qt
// main.cpp
#include <QApplication>
#include <QLabel>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QLabel label("Hello, Qt!");
label.show();
return app.exec(); // starts the event loop
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(HelloQt)
find_package(Qt6 REQUIRED COMPONENTS Widgets)
qt_standard_project_setup()
add_executable(helloqt main.cpp)
target_link_libraries(helloqt PRIVATE Qt6::Widgets)
Signals & Slots
{:.gc-basic}
Basic
Qt’s signals & slots is a type-safe, loosely coupled event/callback system. Objects emit signals; connected slots are called automatically.
#include <QObject>
class Sensor : public QObject {
Q_OBJECT // REQUIRED macro — enables meta-object features
float value_ = 0.0f;
public:
explicit Sensor(QObject* parent = nullptr) : QObject(parent) {}
float value() const { return value_; }
public slots:
void setValue(float v) {
if (v != value_) {
value_ = v;
emit valueChanged(v); // emit the signal
}
}
signals:
void valueChanged(float newValue); // declaration only — Qt generates body
void alarmTriggered(const QString& msg);
};
Connecting Signals to Slots
Sensor* sensor = new Sensor(this);
QLabel* label = new QLabel(this);
// Syntax 1: Qt5+ function pointer (compile-time checked)
connect(sensor, &Sensor::valueChanged,
label, [label](float v){
label->setText(QString::number(v, 'f', 2));
});
// Syntax 2: Old-style SIGNAL/SLOT macros (runtime string match)
connect(sensor, SIGNAL(valueChanged(float)),
label, SLOT(setText(QString))); // type mismatch only caught at runtime!
// Connecting to a lambda
connect(sensor, &Sensor::alarmTriggered,
[](const QString& msg){ qWarning() << "ALARM:" << msg; });
// Triggering the signal
sensor->setValue(23.7f); // emits valueChanged → label updates automatically
Connection Types
| Type | When | Use Case |
|---|---|---|
AutoConnection (default) |
Same thread → Direct; different thread → Queued | General use |
DirectConnection |
Slot called immediately in emitter’s thread | Same-thread callbacks |
QueuedConnection |
Slot called in receiver’s event loop | Cross-thread safe |
BlockingQueuedConnection |
Like Queued but emitter blocks | Synchronous cross-thread |
QObject and Object Ownership
{:.gc-mid}
Intermediate
Qt uses a parent-child ownership system. When a parent QObject is destroyed, it recursively destroys all its children — automatic memory management.
// Parent takes ownership of children
QWidget* window = new QWidget(); // no parent — must delete manually
QLabel* label = new QLabel("Hello", window); // window owns label
QPushButton* btn = new QPushButton("OK", window);
// When window is destroyed, label and btn are destroyed too
delete window; // label and btn deleted automatically
// With smart pointers at the top level:
auto window = std::make_unique<QWidget>();
// children created with raw new + parent are still safe
auto* label = new QLabel("Hello", window.get());
// window destroyed when it goes out of scope → label destroyed too
QObject Rules
// QObjects must NOT be copied — they have identity (connections, parent/child)
// Copy constructor and assignment are DELETED in QObject
Sensor s1;
// Sensor s2 = s1; // compile error!
// Use pointers or references to pass QObjects around
void process(Sensor* s) { s->setValue(5.0f); }
The Event Loop
{:.gc-mid}
Qt is event-driven. QCoreApplication::exec() starts the event loop which processes events: user input, timers, socket notifications, cross-thread signals.
#include <QCoreApplication>
#include <QTimer>
int main(int argc, char* argv[]) {
QCoreApplication app(argc, argv);
// Single-shot timer
QTimer::singleShot(1000, &app, [](){
qDebug() << "1 second elapsed";
QCoreApplication::quit(); // exit event loop
});
// Repeating timer
QTimer* timer = new QTimer(&app);
QObject::connect(timer, &QTimer::timeout, [](){
qDebug() << "tick";
});
timer->start(500); // every 500ms
return app.exec(); // runs until quit()
}
Running Work Without Blocking the UI
// WRONG — blocking the event loop freezes the GUI
void MyWidget::onButtonClicked() {
for (int i = 0; i < 1000000; i++) {
doHeavyWork(); // blocks UI for seconds!
}
}
// RIGHT — use QThread or QtConcurrent
#include <QThread>
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork() {
for (int i = 0; i < 1000000; i++) doHeavyWork();
emit finished();
}
signals:
void finished();
void progress(int percent);
};
// In the widget:
QThread* thread = new QThread(this);
Worker* worker = new Worker; // no parent — will move to thread
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &Worker::doWork);
connect(worker, &Worker::finished, thread, &QThread::quit);
connect(worker, &Worker::finished, worker, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
thread->start();
Serial Port (Embedded Linux)
{:.gc-adv}
Advanced
find_package(Qt6 REQUIRED COMPONENTS SerialPort)
target_link_libraries(myapp PRIVATE Qt6::SerialPort)
#include <QSerialPort>
#include <QSerialPortInfo>
// List available ports
const auto ports = QSerialPortInfo::availablePorts();
for (const auto& info : ports)
qDebug() << info.portName() << info.description();
// Open and configure
QSerialPort serial;
serial.setPortName("/dev/ttyS0");
serial.setBaudRate(QSerialPort::Baud115200);
serial.setDataBits(QSerialPort::Data8);
serial.setParity(QSerialPort::NoParity);
serial.setStopBits(QSerialPort::OneStop);
serial.setFlowControl(QSerialPort::NoFlowControl);
if (!serial.open(QIODevice::ReadWrite)) {
qCritical() << "Failed to open serial port:" << serial.errorString();
return;
}
// Async read (signal-driven — never block!)
QObject::connect(&serial, &QSerialPort::readyRead, [&](){
QByteArray data = serial.readAll();
qDebug() << "Received:" << data.toHex();
});
// Write
serial.write("\xAA\x55\x01\x00", 4);
QML for Embedded HMI
{:.gc-adv}
// main.qml — Declarative UI
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 800; height: 480
title: "Sensor Dashboard"
Column {
anchors.centerIn: parent
spacing: 20
Text {
id: tempLabel
text: "Temperature: " + sensorBackend.temperature.toFixed(1) + " °C"
font.pixelSize: 32
color: sensorBackend.temperature > 80 ? "red" : "white"
}
Button {
text: "Reset"
onClicked: sensorBackend.reset()
}
}
}
// Expose C++ object to QML
#include <QQmlContext>
SensorBackend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("sensorBackend", &backend);
engine.load(QUrl("qrc:/main.qml"));
Interview Q&A
{:.gc-iq}
Interview Q&A
Q1 — Basic: Explain how Qt’s signals and slots differ from plain function callbacks.
Signals and slots are type-safe (checked at compile time with the function-pointer syntax), loosely coupled (the emitter doesn’t know about the receiver), and thread-aware (queued connections automatically marshal across threads). Plain callbacks are tightly coupled — the caller must know the callback’s type and manage its lifetime. Signals can be connected to multiple slots and disconnected at runtime without modifying the emitting class. The
Q_OBJECTmacro and moc (Meta-Object Compiler) generate the underlying connection infrastructure.
Q2 — Basic: What is the Qt event loop and why must it not be blocked?
The event loop (
QCoreApplication::exec()) processes all events: user input, timer callbacks, socket notifications, and queued signal-slot connections. It runs in the main thread. If you block the main thread (long computation,sleep(), blocking I/O), no events are processed — the GUI freezes, timers don’t fire, and the application becomes unresponsive. Move long-running work to aQThreador useQtConcurrent, communicating results back to the main thread via queued signals.
Q3 — Intermediate: How does Qt’s parent-child memory management work?
Every
QObjectcan have a parent (set via constructor orsetParent()). When a parent is destroyed, it iterates its children list and destroys them recursively. This means GUI widgets created with a parent are automatically cleaned up when the parent widget is destroyed — you don’t need to delete them manually. The top-level window (no parent) must be managed manually (deleteorunique_ptr). You cannot useshared_ptrdirectly with QObject children managed this way.
Q4 — Advanced: How do you safely communicate between a worker QThread and the main thread?
Use queued signal-slot connections. Move the worker object to the thread with
moveToThread(). Signals emitted from the worker thread are queued in the main thread’s event loop and delivered safely without race conditions. Never access GUI widgets (which live in the main thread) directly from a worker thread — always use queued signals/slots. For sharing data, useQMutex+ condition variables orQAtomicInt/QAtomicPointerfor lock-free access.
References
{:.gc-ref}
References
| Resource | Link |
|---|---|
| Qt Official Documentation | doc.qt.io |
| Qt Signals & Slots | doc.qt.io/qt-6/signalsandslots.html |
| Qt Threads & Concurrency | doc.qt.io/qt-6/thread-basics.html |
| Qt for Embedded Linux | doc.qt.io/qt-6/embedded-linux.html |