Qt 5 and C++11: Lambdas Are Your Friend

lambdaSince Qt 5 was released I had been putting off upgrading to Qt 5 on a project I have been working on. Even minor ports like this one, from Qt 4.7 to Qt 5 (that’s right…skipped 4.8) for some reason are never as easy as implied. “Just change the include and link paths,” they said. “It’ll just build,” they said. Psht, yeah right. Not falling for it again.

Well, I finally made the jump and, after many years of working in Qt, I feel like C++ and Qt are working together now instead of Qt just being something that helps you develop better C++. I believe the signal/slot mechanism has found its soul mate in C++11 lambda functions.

What’s this signal/slot thingy?

If you don’t work in Qt you probably don’t care anyway but the fundamental communication mechanism between objects in the Qt framework is defined by signals (events that can be emitted) and slots (handlers for events).

As a quick introduction, consider the following example (assume the setupUi method creates three QPushButton objects and a QTextEdit):

We’ve connected the click signals for each of the buttons to the three methods defined there.

SIGNAL and SLOT used in the connect method calls are macros that resolve the names of the functions that are being connected. For our purposes, let’s just assume it’s magic.

So, what was wrong with the signal/slot mechanism?

Well, nothing was exactly broken with this, right? It works…I guess.

The SIGNAL/SLOT macro “magic” I talked about earlier isn’t really that magical. Both of those macros actually resolve to a string. Problem #1: it uses strings to resolve connections…at runtime. So if you happen to have a slot that takes a string and the signal declaration takes an int…you don’t know it until you run your application. Unless you’re used to JavaScript development, this probably catches you off guard.

Problem #2: Why on earth do I have to define three methods that do basically the same exact thing?

Cleanup Pre-Qt5

Before Qt5 and C++11, we could the QSignalMapper class to do something like this:

That’s not bad. We’ve eliminated the three slot functions that do basically the same thing with different text and replaced it with a single function. You and I both know that this feels a bit hackish though. QSignalMapper is like a patch over a genuine problem.

Adding C++11 Lambda Functions

If you been following the evolution of C++, you might know about lambda functions and expressions. If you aren’t familiar with them you can find tons of information by searching, but the short answer is that they are basically in-line, unnamed functions. The general format is:

where capture specifies which symbols visible in the scope where the function is declared will be visible to the body of the lambda, parameters is the list of parameters passed into the lambda, and body is the definition of the function.

What does that mean for Qt?

The most important thing to consider is that they can be used as function pointers…for slots. We can connect a slot like this:

Firstly, notice that the we can now pass in actual pointers to signals and slots instead of just using the SIGNAL and SLOT macros (which you *can* still use if you want). That means compile time checking of connections. No more running the program and finding out you used an int for your slot but the signal passed an int.

Secondly, a lambda is basically just a function pointer. Consider how this changes our example:

* Note: If you are using Clang on Mac, you may need to add “CONFIG += c++11” to your .pro file to enable the C++11 features, including lambda functions.

Compare this to our fix above using QSignalMapper. It’s simply cleaner and easier to follow. However, we don’t need to stop there. Since our slot function is so simple and really the important stuff (the string we are setting) is in the lambda, there is no reason to even have it.

This is a far more elegant solution than our original class with three different slots.

Caveats

Of course, not everything is perfect. There are a couple of things you have to keep in mind when using pointers to functions and lambdas as slots. Firstly, it’s a little more complicated in that you have to specify the full type of the slot class (if you don’t use a lambda) but less ambiguity never hurt anyone.

The two bigger issues are:

1) default arguments are not supported when connecting by function pointer and

2) slots created using lambdas are not automatically disconnected when the ‘receiver’ is destroyed.

The second isn’t necessarily a major problem since the fact that you are using lambda functions suggest you aren’t planning on connecting/disconnecting often anyway (though it is still possible manually).

The first, however, can be annoying. Consider a connection like this:

where the signal and slot are defined as:

Even though the arguments match when considering the default argument, the connect method will throw a compile-time error. Of course, a simple fix is using a lambda like this and just ignoring the signal argument:

Problem solved.

Brian Poteat

Brian Poteat

Brian Poteat

Latest posts by Brian Poteat (see all)

Creative Commons License

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

2 Comments

  1. John

    “No more running the program and finding out you used an int for your slot but the signal passed an int.” I don’t see the problem.
    I don’t see how one could use a lambda as a function pointer. If it takes the address of operator() from the object the lamda resolves to you’d lose the bindings. I would expect connect to take the entire function object. And if it can do that you can probably use std::bind as well.
    By the way, I’d be concerned about misleading people with your by-value comment in the last example before Caveats. You’re taking the implicit “this” by-value, which is a lot like taking your member variables by-reference. This has confused a number of people already.
    It might be useful to note that the -std=c++11 argument is only available in newer versions of clang++ and g++. -std=c++0x means the same thing in the versions where -std=c++11 is available, but it also works in earlier versions of those compilers. I’m not sure if lamdas were available before the -std=c++11 option (I normally only deal with a couple different versions of each), but I think it’s useful to recommend -std=c++0x for Makefiles and SConscripts and such just to be slightly more portable.

    • bpoteat-al

      Thanks for the reply. You caught a bug in my text. It was supposed to say “passed a string.”

      You’re right. You could use bind if you wanted but (as far as I am aware) that also wasn’t possible before Qt 5 and C++11. Using either would be personal preference.

      Your other concerns & tips are certainly valid; I felt much more detail would be outside the scope of this brief introduction and more in line with a tutorial.