Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering Qt  5

You're reading from   Mastering Qt 5 Create stunning cross-platform applications using C++ with Qt Widgets and QML with Qt Quick

Arrow left icon
Product type Paperback
Published in Aug 2018
Publisher Packt
ISBN-13 9781788995399
Length 534 pages
Edition 2nd Edition
Languages
Tools
Arrow right icon
Authors (2):
Arrow left icon
Robin Penea Robin Penea
Author Profile Icon Robin Penea
Robin Penea
Guillaume Lazar Guillaume Lazar
Author Profile Icon Guillaume Lazar
Guillaume Lazar
Arrow right icon
View More author details
Toc

Table of Contents (16) Chapters Close

Preface 1. Get Your Qt Feet Wet FREE CHAPTER 2. Discovering qmake Secrets 3. Dividing Your Project and Ruling Your Code 4. Conquering the Desktop UI 5. Dominating the Mobile UI 6. Even Qt Deserves a Slice of Raspberry Pi 7. Third-Party Libraries without a Headache 8. Animations - Its Alive, Alive! 9. Keeping Your Sanity with Multithreading 10. Need IPC? Get Your Minions to Work 11. Having Fun with Multimedia and Serialization 12. You Shall (Not) Pass with QTest 13. All Packed and Ready to Deploy 14. Qt Hat Tips and Tricks 15. Other Books You May Enjoy

Emitting a custom signal using lambdas

The remove task is straightforward to implement, but we'll study some new concepts along the way. The Task has to notify its owner and parent (the MainWindow) that the removeTaskButton QPushButton has been clicked. We'll implement this by defining a custom removed signal in the Task.h files:

class Task : public QWidget 
{ 
    ... 
public slots: 
    void rename(); 
signals: 
    void removed(Task* task); 
   ... 
}; 

Like we did for the slots, we have to add the Qt keyword signals in our header. Since signal is used only to notify another class, the public keyword is not needed (it even raises a compilation error). signal is simply a notification sent to the receiver (the connected slot); it implies that there is no function body for the removed(Task* task) function.

We added the task parameter to allow the receiver to know which task asked to be removed. The next step is to emit the removed signal upon the removeButton click. This is done in the Task.cpp file:

Task::Task(const QString& name, QWidget *parent) : 
        QWidget(parent), 
        ui(new Ui::Task) 
{ 
    ui->setupUi(this); 
    ... 
    connect(ui->removeButton, &QPushButton::clicked, [this] { 
        emit removed(this); 
    }); 
} 

This code excerpt shows a very interesting feature of C++11: lambdas. In our example, lambda is the following part:

[this] { 
        emit removed(this); 
    }); 

Here, we connected the clicked signal to an anonymous inline function, a lambda. Qt allows signal-relaying by connecting a signal to another signal if their signatures match. It's not the case here: the clicked signal has no parameter and the removed signal needs a Task*. A lambda avoids the declaration of a verbose slot in Task. Qt 5 accepts a lambda instead of a slot in a connect, and both syntaxes can be used.

Our lambda executes a single line of code: emit removed(this). Emit is a Qt macro that will trigger the connected slot with what we passed as the parameter. As we said earlier, removed(Task* this) has no function body, its purpose is to notify the registered slot of an event.

Lambdas are a great addition to C++. They offer a very practical way of defining short functions in your code. Technically, a lambda is the construction of a closure capable of capturing variables in its scope. The full syntax goes like this:

[ capture-list ] ( params ) -> ret { body }

Let's study each part of this statement:

  • capture-list: Defines what variables will be visible inside the lambda scope.
  • params: This is the function parameter's type list that can be passed to the lambda scope. There are no parameters in our case. We might have written [this] () { ... }, but C++11 lets us skip the parentheses altogether.
  • ret: This is the return type of the lambda function. Just like params, this parameter can be omitted if the return type is void.
  • body: This is obviously your code body where you have access to your capture-list and params, and which must return a variable with a ret type.

In our example, we captured the this pointer to be able to:

  • Have a reference on the removed() function, which is part of the Task class. If we did not capture this, the compiler would have shouted error: 'this' was not captured for this lambda function emit removed(this);.
  • Pass this to the removed signal: the caller needs to know which task triggered removed.

capture-list relies on standard C++ semantics: capture variables by copy or by reference. Let's say that we wanted to print a log of the name constructor parameter and we capture it by reference in our lambda:

connect(ui->removeButton, &QPushButton::clicked, [this, &name] { 
        qDebug() << "Trying to remove" << name; 
        this->emit removed(this); 
    }); 

This code will compile fine. Unfortunately, the runtime will crash with a dazzling segmentation fault when we try to remove a Task. What happened? As we said, our lambda is an anonymous function that will be executed when the clicked() signal has been emitted. We captured the name reference, but this reference may be invalid once we get out of the Task constructor (more precisely, from the caller scope). The qDebug() function will then try to display an unreachable code and crash.

You really need to be careful with what you capture and the context in which your lambda will be executed. In this example, the segmentation fault can be amended by capturing name by copy:

connect(ui->removeButton, &QPushButton::clicked, [this, name] { 
        qDebug() << "Trying to remove" << name; 
        this->emit removed(this); 
    }); 
  • You can capture by copy or reference all variables that are reachable in the function where you define your lambda with the = and & syntax.
  • The this variable is a special case of the capture list. You cannot capture it by the [&this] reference and the compiler will warn you if you are in this situation: [=, this]. Don't do this. Kittens will die.

Our lambda is passed directly as a parameter to the connect function. In other words, the lambda is a variable. This has many consequences: we can call it, assign it, and return it. To illustrate a "fully formed" lambda, we can define one that returns a formatted version of the task name. The sole purpose of this snippet is to investigate the lambda function's machinery. Don't include the following code in your todo app, your colleagues might call you a "functional zealot":

connect(ui->removeButton, &QPushButton::clicked, [this, name] { 
    qDebug() << "Trying to remove" << 
        [] (const QString& taskName) -> QString { 
            return "-------- " + taskName.toUpper(); 
    }(name); 
    emit removed(this); 
}); 

Here we did a tricky thing. We called qDebug(). Inside this call, we defined a lambda that is immediately executed. Let's analyze it:

  • []: We performed no capture. lambda does not depend on the enclosing function.
  • (const Qstring& taskName): When this lambda is called, it will expect a QString to work on.
  • -> QString: The returned value of the lambda will be a QString.
  • return "------- " + taskName.toUpper(): The body of our lambda. We return a concatenation of a string and the uppercase version of the taskName parameter. As you can see, string-manipulation becomes a lot easier with Qt.
  • (name): Here comes the catch. Now that the lambda function is defined, we can call it by passing the name parameter. In a single expression, we define it then call it. The qDebug() function will simply print the result.

The real benefit of this lambda will emerge if we are able to assign it to a variable and call it multiple times. C++ is statically typed, so we must provide the type of our lambda variable. In the language specification, a lambda type cannot be explicitly defined. We will soon see how we can do it with C++11. For now, let's finish our remove feature.

The task now emits the removed() signal. This signal has to be consumed by MainWindow:

// in MainWindow.h 
public slots: 
    void addTask(); 
    void removeTask(Task* task); 
 
// In MainWindow.cpp 
void MainWindow::addTask() 
{ 
    ... 
    if (ok && !name.isEmpty()) { 
        qDebug() << "Adding new task"; 
        Task* task = new Task(name); 
        connect(task, &Task::removed,  
       this, &MainWindow::removeTask); 
    ... 
    } 
} 
 
void MainWindow::removeTask(Task* task) 
{ 
    mTasks.removeOne(task); 
    ui->tasksLayout->removeWidget(task); 
    delete task; 
} 

MainWindow::removeTask() must match the signal signature. The connection is made when the task is created. The interesting part comes in the implementation of MainWindow::removeTask().

The task is first removed from the mTasks vector. It is then removed from tasksLayout. The last step is to delete Task. The destructor will unregister itself from centralWidget of MainWindow. In this case, we don't rely on the Qt hierarchical parent-children system for the QObject life cycle because we want to delete Task before the destruction of MainWindow.

You have been reading a chapter from
Mastering Qt 5 - Second Edition
Published in: Aug 2018
Publisher: Packt
ISBN-13: 9781788995399
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime