Qt Core Containers

QList, QVector, QMap, QHash, QSet, QString, QByteArray — Qt's container classes explained with examples and embedded use cases.

7 min read
52794 chars

Why Qt Containers?

Qt provides its own container library alongside the C++ STL. Qt containers integrate with Qt’s data model, are implicitly shared (copy-on-write), and work seamlessly with Qt’s range-based for and algorithms.

Qt Container      STL Equivalent     Qt Advantage
─────────────     ──────────────     ──────────────────────────
QList<T>          std::vector<T>     Implicit sharing, Qt API
QMap<K,V>         std::map<K,V>      Implicit sharing
QHash<K,V>        std::unordered_map Faster lookup
QSet<T>           std::unordered_set
QString           std::wstring       Unicode, rich API, QML
QByteArray        std::vector<char>  Base64, hex, serial data

QList — The Go-To Sequential Container

#include <QList>

QList<int> sensors = {10, 20, 30, 40, 50};

// Append / prepend
sensors.append(60);
sensors.prepend(5);

// Access
qDebug() << sensors.first();  // 5
qDebug() << sensors.last();   // 60
qDebug() << sensors[2];       // 20

// Search
int idx = sensors.indexOf(30);  // 3
bool has = sensors.contains(99); // false

// Remove
sensors.removeAt(0);        // remove index 0
sensors.removeAll(20);      // remove all 20s
sensors.removeFirst();
sensors.removeLast();

// Iterate
for (int v : sensors)
    qDebug() << v;

// Filter with STL algorithms
auto above30 = std::copy_if(sensors.begin(), sensors.end(),
    QList<int>{}.begin(), [](int v){ return v > 30; });

QVector — Contiguous Memory (Qt 5 vs Qt 6)

In Qt 6, QList<T> is the same as QVector<T> — contiguous storage by default.

QVector<float> readings(100, 0.0f);  // 100 zeros
readings[42] = 3.14f;

// Efficient bulk operations
readings.reserve(1000);     // pre-allocate
readings.resize(500);       // resize to 500 elements
readings.squeeze();         // free excess capacity

QMap — Ordered Key-Value Store

#include <QMap>

QMap<QString, float> sensorValues;
sensorValues["temperature"] = 36.5f;
sensorValues["humidity"]    = 60.2f;
sensorValues["pressure"]    = 1013.0f;

// Access
float t = sensorValues.value("temperature", -1.0f);  // default -1

// Iterate (sorted by key)
for (auto it = sensorValues.begin(); it != sensorValues.end(); ++it)
    qDebug() << it.key() << "=" << it.value();

// Range-based
for (const auto &[key, val] : sensorValues.asKeyValueRange())
    qDebug() << key << ":" << val;

// Multi-value map
QMultiMap<QString, int> alerts;
alerts.insert("overheat", 80);
alerts.insert("overheat", 90);  // two entries for same key

QHash — Fast Unordered Key-Value

#include <QHash>

QHash<QString, QObject*> devices;
devices["uart-0"] = new UartDevice(this);
devices["spi-1"]  = new SpiDevice(this);

// O(1) lookup
QObject *dev = devices.value("uart-0", nullptr);

// Check
if (devices.contains("can-0"))
    qDebug() << "CAN device found";

// Remove
devices.remove("spi-1");

// All keys / values
QStringList keys = devices.keys();

QSet — Unique Elements

#include <QSet>

QSet<QString> activeDevices;
activeDevices.insert("uart-0");
activeDevices.insert("spi-1");
activeDevices.insert("uart-0");  // ignored — already present

qDebug() << activeDevices.size();  // 2

// Set operations
QSet<QString> all   = {"uart-0", "spi-1", "i2c-2"};
QSet<QString> alive = {"uart-0", "i2c-2"};

QSet<QString> dead  = all - alive;       // {"spi-1"}
QSet<QString> union_ = all | alive;      // all
QSet<QString> inter = all & alive;       // {"uart-0","i2c-2"}

QString — Unicode Text

#include <QString>

QString name = "Sensor-001";

// Build strings
QString msg  = QString("Device %1 at %2°C").arg(name).arg(36.5, 0, 'f', 1);
QString hex  = QString("0x%1").arg(0xFF, 4, 16, QChar('0'));  // "0x00FF"

// Query
qDebug() << name.length();        // 10
qDebug() << name.toUpper();       // "SENSOR-001"
qDebug() << name.contains("001"); // true
qDebug() << name.startsWith("S"); // true

// Split / join
QStringList parts = "a,b,c".split(",");  // ["a","b","c"]
QString joined = parts.join(" | ");       // "a | b | c"

// Number conversions
int    i = QString("42").toInt();
float  f = QString("3.14").toFloat();
QString s = QString::number(3.14159, 'f', 2);  // "3.14"

// Replace / trim
QString raw = "  hello world  ";
qDebug() << raw.trimmed();                 // "hello world"
qDebug() << raw.simplified();             // "hello world"
qDebug() << raw.replace("world", "Qt");   // "  hello Qt  "

QByteArray — Raw Binary / Serial Data

#include <QByteArray>

// Build from literal
QByteArray packet = "\x02\x01\x00\xFF\x03";

// Hex representation
qDebug() << packet.toHex(' ');  // "02 01 00 ff 03"

// From/to hex
QByteArray decoded = QByteArray::fromHex("deadbeef");
qDebug() << decoded.toHex();  // "deadbeef"

// Base64
QByteArray data = "Hello Qt";
QByteArray b64  = data.toBase64();
QByteArray back = QByteArray::fromBase64(b64);

// Append / slice
QByteArray frame;
frame.append('\x02');          // STX
frame.append("payload");
frame.append('\x03');          // ETX

QByteArray payload = frame.mid(1, frame.size() - 2);  // strip STX/ETX

// Search
int pos = frame.indexOf('\x03');

Visual: Container Selection Guide

Need ordered key-value?
  YES → QMap<K,V>
  NO  → Need fast O(1) lookup?
          YES → QHash<K,V>
          NO  → Need unique elements?
                  YES → QSet<T>
                  NO  → Sequential list?
                          YES → QList<T>  (or QVector<T>)
                          NO  → Queue/Stack?
                                  YES → QQueue / QStack

Lab 1 — Device Registry with QHash

struct DeviceInfo {
    QString name;
    QString type;
    float   lastReading = 0.0f;
    bool    online      = false;
};

class DeviceRegistry {
    QHash<QString, DeviceInfo> m_devices;

public:
    void add(const QString &id, const QString &name, const QString &type) {
        m_devices[id] = {name, type, 0.0f, true};
    }

    void update(const QString &id, float value) {
        if (m_devices.contains(id))
            m_devices[id].lastReading = value;
    }

    void setOnline(const QString &id, bool online) {
        if (m_devices.contains(id))
            m_devices[id].online = online;
    }

    QStringList onlineDevices() const {
        QStringList result;
        for (auto it = m_devices.begin(); it != m_devices.end(); ++it)
            if (it->online) result << it.key();
        return result;
    }

    void printAll() const {
        for (const auto &[id, info] : m_devices.asKeyValueRange()) {
            qDebug() << id
                     << info.name
                     << (info.online ? "ONLINE" : "OFFLINE")
                     << info.lastReading;
        }
    }
};

// main
DeviceRegistry reg;
reg.add("uart-0", "UART Bridge",   "serial");
reg.add("spi-1",  "SPI Flash",     "storage");
reg.add("i2c-2",  "Temp Sensor",   "sensor");

reg.update("i2c-2", 36.5f);
reg.setOnline("spi-1", false);

qDebug() << "Online:" << reg.onlineDevices();
reg.printAll();

Lab 2 — Packet Builder with QByteArray

class PacketBuilder {
public:
    // Frame: [STX][LEN][CMD][DATA...][CRC][ETX]
    static QByteArray build(quint8 cmd, const QByteArray &data) {
        QByteArray frame;
        frame.append('\x02');                    // STX
        frame.append(static_cast<char>(data.size() + 1)); // LEN
        frame.append(static_cast<char>(cmd));   // CMD
        frame.append(data);                      // DATA
        frame.append(crc8(frame));               // CRC
        frame.append('\x03');                    // ETX
        return frame;
    }

    static bool parse(const QByteArray &raw,
                      quint8 &cmd, QByteArray &data)
    {
        if (raw.size() < 5) return false;
        if (raw.front() != '\x02') return false;
        if (raw.back()  != '\x03') return false;
        int len = static_cast<quint8>(raw[1]);
        cmd  = static_cast<quint8>(raw[2]);
        data = raw.mid(3, len - 1);
        return true;
    }

private:
    static char crc8(const QByteArray &data) {
        quint8 crc = 0;
        for (quint8 b : data) crc ^= b;
        return static_cast<char>(crc);
    }
};

// Usage
QByteArray payload = QByteArray::fromHex("0102030405");
QByteArray frame   = PacketBuilder::build(0xA1, payload);
qDebug() << "Frame:" << frame.toHex(' ');

quint8 cmd; QByteArray parsed;
if (PacketBuilder::parse(frame, cmd, parsed))
    qDebug() << "CMD:" << Qt::hex << cmd << "DATA:" << parsed.toHex();

Interview Questions

Q1: What is implicit sharing (copy-on-write) in Qt containers?

When you copy a Qt container, both copies share the same internal data. A deep copy only happens when one of them is modified (write). This makes passing containers by value cheap — like passing a pointer — until a write triggers a real copy.

Q2: When should you use QHash over QMap?

Use QHash when you need O(1) average lookup and don’t need keys in sorted order. Use QMap when you need iteration in sorted key order or range queries. QHash is generally 2-3x faster for lookups.

Q3: What is the difference between QList::at() and QList::operator[]?

at() is read-only and asserts bounds in debug mode — safe and fast. operator[] returns a non-const reference and may detach the implicit share for write access. Use at() for read-only access.

Q4: How do you safely convert between QString and std::string?

QString::toStdString() → UTF-8 std::string. QString::fromStdString(s) ← UTF-8. For embedded: prefer toLatin1() / toUtf8() to get a QByteArray for transmission.

Q5: Why is QStringList common in Qt code?

QStringList is QList<QString> with extra convenience methods: join(), filter(), replaceInStrings(), sort(). It integrates with split(), QDir, QProcess, and many Qt APIs.


Application: Config File Parser

class ConfigParser {
    QMap<QString, QMap<QString, QString>> m_sections;

public:
    bool load(const QString &path) {
        QFile file(path);
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
            return false;

        QString currentSection = "global";
        QTextStream in(&file);
        while (!in.atEnd()) {
            QString line = in.readLine().trimmed();
            if (line.isEmpty() || line.startsWith('#')) continue;

            if (line.startsWith('[') && line.endsWith(']')) {
                currentSection = line.mid(1, line.length() - 2);
                continue;
            }

            int eq = line.indexOf('=');
            if (eq > 0) {
                QString key = line.left(eq).trimmed();
                QString val = line.mid(eq + 1).trimmed();
                m_sections[currentSection][key] = val;
            }
        }
        return true;
    }

    QString value(const QString &section, const QString &key,
                  const QString &def = {}) const {
        return m_sections.value(section).value(key, def);
    }

    QStringList sections() const { return m_sections.keys(); }

    void print() const {
        for (const auto &[sec, kvs] : m_sections.asKeyValueRange()) {
            qDebug() << "[" + sec + "]";
            for (const auto &[k, v] : kvs.asKeyValueRange())
                qDebug() << " " << k << "=" << v;
        }
    }
};

// Config file: device.ini
// [uart]
// port=/dev/ttyUSB0
// baud=115200
// [i2c]
// bus=1
// address=0x48

ConfigParser cfg;
if (cfg.load("device.ini")) {
    QString port = cfg.value("uart", "port", "/dev/ttyS0");
    int     baud = cfg.value("uart", "baud", "9600").toInt();
    qDebug() << "UART:" << port << "@" << baud;
}

References

ResourceLink
Qt Docs: Container Classeshttps://doc.qt.io/qt-6/containers.html
Qt Docs: QStringhttps://doc.qt.io/qt-6/qstring.html
Qt Docs: QByteArrayhttps://doc.qt.io/qt-6/qbytearray.html
Qt Docs: QHashhttps://doc.qt.io/qt-6/qhash.html

Next tutorial → File I/O & Resources