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

Arrow left icon
Product type Paperback
Published in Dec 2016
Publisher Packt
ISBN-13 9781786467126
Length 526 pages
Edition 1st 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 (15) 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 Serialization 12. You Shall (Not) Pass with QTest 13. All Packed and Ready to Deploy 14. Qt Hat Tips and Tricks

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 (MainWindow) that the removeTaskButtonQPushButton has been clicked. We'll implement this by defining a custom signal removed in 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 a signal is used only to notify another class, the public keyword is not needed (it even raises a compilation error). A 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, the lambda is the following part:

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

What we did here is to connect 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 immediately trigger the connected slot with what we passed in a 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: This defines what variables will be visible inside the lambda scope.
  • params: This is the function parameters 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 type ret.

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

  • Have a reference on the removed() function, which is a 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.

The capture-list relies on standard C++ semantics: capture variables by copy or by reference. Let us say that we wanted to print a log of the constructor parameter name 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 - and is - invalid once we get out of Task constructor (more precisely, from the caller scope). The qDebug() function will then try to display an unreachable code and crash.

You really want 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 the name by copy:

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

Tip

C++ Tip

  • You can capture by copy or reference all variables that are reachable in the function where you define your lambda with the syntax [=] and [&].
  • The this variable is a special case of the capture list. You cannot capture it by reference [&this] 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 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 something like a "functional zealot":

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

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

  • []: We performed no capture. The 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 parameter taskName. 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 passing the name parameter. In a single instruction, we define it and 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'll see soon how we can do it with C++11. For now, let's finish our remove feature.

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); 
    task->setParent(0); 
    delete task; 
} 

The 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. Here, tasksLayout releases its ownership of task (that is, tasksLayout ceases to be the task class's parent).

So far so good. The next two lines are interesting. The ownership transfer does not completely release the task class ownership. If we commented these lines, here is how removeTask() will look:

void MainWindow::removeTask(Task* task) 
{ 
    mTasks.removeOne(task); 
    ui->tasksLayout->removeWidget(task); 
    // task->setParent(0); 
    // delete task; 
} 

If you add a log message in Task destructor and execute the program, this log message will be displayed. Nonetheless, the Qt documentation tells us in Qlayout::removeWidget part: The ownership of a widget remains the same as when it was added.

Instead, what really happens is that the task class's parent becomes centralWidget, the tasksLayout class's parent. We want Qt to forget everything about task, that's why we call task->setParent(0). We can then safely delete it and call it a day.

You have been reading a chapter from
Mastering Qt 5
Published in: Dec 2016
Publisher: Packt
ISBN-13: 9781786467126
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 $19.99/month. Cancel anytime