打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Lambda Functions in C++11 - the Definitive Guide

Lambda Functions in C++11 - the Definitive Guide

One of the most exciting features of C++11 is ability to create lambdafunctions (sometimes referred to as closures). What does this mean? A lambdafunction is a function that you can write inline in your source code (usually to pass in to another function, similar to the idea of a functor or function pointer). With lambda, creating quick functions has become mucheasier, and this means that not only can you start using lambda when you'dpreviously have needed to write a separate named function, but you can startwriting more code that relies on the ability to create quick-and-easyfunctions. In this article, I'll first explain why lambda is great--with someexamples--and then I'll walk through all of the details of what you can do withlambda.

Why Lambdas Rock

Imagine that you had an address book class, and you want to be able toprovide a search function. You might provide a simple search function, taking astring and returning all addresses that match the string. Sometimes that's whatusers of the class will want. But what if they want to search only in thedomain name or, more likely, only in the username and ignore results in thedomain name? Or maybe they want to search for all email addresses that alsoshow up in another list. There are a lot of potentially interesting things tosearch for. Instead of building all of these options into the class, wouldn'tit be nice to provide a generic "find" method that takes a procedure fordeciding if an email address is interesting? Let's call the methodfindMatchingAddresses, and have it take a "function" or "function-like" object.

#include <string>#include <vector>class AddressBook{    public:    // using a template allows us to ignore the differences between functors, function pointers     // and lambda    template<typename Func>    std::vector<std::string> findMatchingAddresses (Func func)    {         std::vector<std::string> results;        for ( auto itr = _addresses.begin(), end = _addresses.end(); itr != end; ++itr )        {            // call the function passed into findMatchingAddresses and see if it matches            if ( func( *itr ) )            {                results.push_back( *itr );            }        }        return results;    }    private:    std::vector<std::string> _addresses;};

Anyone can pass a function into the findMatchingAddresses that containslogic for finding a particular function. If the function returns true, whengiven a particular address, the address will be returned. This kind of approachwas OK in earlier version of C++, but it suffered from one fatal flaw: itwasn't quite convenient enough to create functions. You had to go define itsomewhere else, just to be able to pass it in for one simple use. That's wherelambdas come in.

Basic Lambda Syntax

Before we write some code to solve this problem, let's see the really basic syntax for lambda.

#include <iostream>using namespace std;int main(){    auto func = [] () { cout << "Hello world"; };    func(); // now call the function}

Okay, did you spot the lambda, starting with []? That identifier, calledthe capture specification, tells the compiler we're creating a lambda function.You'll see this (or a variant) at the start of every lambda function.

Next up, like any other function, we need an argument list: (). Where isthe return value? Turns out that we don't need to give one. In C++11, if thecompiler can deduce the return value of the lambda function, it will do itrather than force you to include it. In this case, the compiler knows thefunction returns nothing. Next we have the body, printing out "Hello World".This line of code doesn't actually cause anything to print out though--we'rejust creating the function here. It's almost like defining a normalfunction--it just happens to be inline with the rest of the code.

It's only on the next line that we call the lambda function: func() -- it looks just like calling any other function. By the way, notice how easy this is to do with auto! You don't need to sweat the ugly syntax of a function pointer.

Applying Lambda in our Example

Let's look at how we can apply this to our address book example, firstcreating a simple function that finds email addresses that contain ".org".

AddressBook global_address_book;vector<string> findAddressesFromOrgs (){    return global_address_book.findMatchingAddresses(         // we're declaring a lambda here; the [] signals the start        [] (const string& addr) { return addr.find( ".org" ) != string::npos; }     );}

Once again we start off with the capture specifier, [], but this time wehave an argument--the address, and we check if it contains ".org". Once again,nothing inside the body of this lambda function is executed here yet; it's onlyinside findMatchingAddresses, when the variable func is used, that the codeinside the lambda function executes.

In other words, each loop through findMatchingAddresses, it calls the lambdafunction and gives it the address as an argument, and the function checks if itcontains ".org".

Variable Capture with Lambdas

Although these kinds of simple uses of lambd are nice, variable capture isthe real secret sauce that makes a lambda function great. Let's imagine that youwant to create a small function that finds addresses that contain a specificname. Wouldn't it be nice if you could write code something like this?

// read in the name from a user, which we want to searchstring name;cin>> name;return global_address_book.findMatchingAddresses(     // notice that the lambda function uses the the variable 'name'    [&] (const string& addr) { return addr.find( name ) != string::npos; } );

It turns out that this example is completely legal--and it shows the realvalue of lambda. We're able to take a variable declared outside of the lambda(name), and use it inside of the lambda. When findMatchingAddresses callsour lambda function, all the code inside of it executes--and when addr.find iscalled, it has access to the name that the user passed in. The only thing weneeded to do to make it work is tell the compiler we wanted to have variablecapture. I did that by putting [&] for the capture specification, ratherthan []. The empty [] tells the compiler not to capture any variables, whereasthe [&] specification tells the compiler to perform variable capture.

Isn't that marvelous? We can create a simple function to pass into thefind method, capturing the variable name, and write it all in only a few linesof code. To get a similar behavior without C++11, we'd either need to create anentire functorclass or we'd need a specialized method on AddressBook. In C++11, we canhave a single simple interface to AddressBook that can support any kind offiltering really easily.

Just for fun, let's say that we want to find only email addresses that are longer than a certain number of characters. Again, we can do this easily:

int min_len = 0;cin >> min_len;return global_address_book.find( [&] (const string& addr) { return addr.length() >= min_len; } );

By the way, to steal a line from Herb Sutter, you should get used to seeing "} );" This is the standard end-of-function-taking-lambda syntax, and the more you start seeing and using lambda in your own code, the more you'll see little piece of syntax.

Lambda and the STL

One of the biggest beneficiaries of lambda functions are, no doubt, powerusers of the standard templatelibrary algorithms package. Previously, using algorithms like for_each wasan exercise in contortions. Now, though, you can use for_each and other STL algorithms almost as if you were writing a normal loop. Compare:

vector<int> v;v.push_back( 1 );v.push_back( 2 );//...for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ ){    cout << *itr;}
with
vector<int> v;v.push_back( 1 );v.push_back( 2 );//...for_each( v.begin(), v.end(), [] (int val){    cout << val;} );

That's pretty good looking code if you ask me--it reads, and is structured,like a normal loop, but you're suddenly able to take advantage of thegoodness that for_each provides over a regular for loop--for example,guarantees that you have the right end condition. Now, you might wonder, won't this kill performance? Well, here's the kicker: it turns out that for_each has about the same performance, and is sometimes even faster than a regular for loop. (The reason: it can take advantage of loop unrolling.)

If you're interested in more on C++11 lambda and the benefits to the STL,you'll enjoy this video of Herb Suttertalking about C++11 lambdas.

I hope this STL example shows you that lambda functions are more than just aslightly more convenient way of creating functions--they allow you to createentirely new ways of writing programs, where you have code that takes otherfunctions as data and allows you to abstract away the proccessing of aparticular data structure. for_each works on a list, but wouldn't it be greatto have similar functions for working with trees, where all you had to do wasright some code that would process each node, and not have to worry about thetraversal algorithm? This kind of decomposition where one function worriesabout the structure of data, while delegating the data processing to anotherfunction can be quite powerful. With lambda, C++11 enables this new kind ofprogramming. Not that you couldn't have done it before--for_each isn'tnew--it's just that you wouldn't have wanted to do it before.

More on the new Lambda Syntax

By the way, the parameter list, like the return value is also optional if you want a function that takes zero arguments. Perhaps the shortest possible lambda expression is:

[] {}

Which is a function that takes no arguments and does nothing. An only slightly more compelling example:

using namespace std;#include <iostream>int main(){    [] { cout << "Hello, my Greek friends"; }();}

Personally, I'm not yet sold on omitting the argument list; I think the [] () structure tends to help lambda functions stand out a little more in the code, but time will tell what standards people come up with.

Return Values

By default, if your lambda does not have a return statement, it defaults to void. If you have a simple return expression, the compiler will deduce the type of the return value:

[] () { return 1; } // compiler knows this returns an integer

If you write a more complicated lambda function, with more than one return value, you should specify the return type. (Some compilers, like GCC, will let you get away without doing this, even if you have more than one return statement, but the standard doesn't guarantee it.)

Lambdas take advantage of the optional new C++11 return value syntax of putting the return value after the function. In fact, you must do this if you want to specify the return type of a lambda. Here's a more explicit version of the really simple example from above:

[] () -> int { return 1; } // now we're telling the compiler what we want

Throw Specifications

Although the C++ standards committee decided to deprecate throwspecifications (except for a few cases I'll cover in a later article), theydidn't remove them from the language, and there are tools that do static codeanalysis to check exceptionspecifications, such as PC Lint.If you are using one of these tools to do compile time exception checking, youreally want to be able to say which exceptions your lambda function throws. Themain reason I can see for doing this is when you're passing a lambda functioninto another function as an argument, and that function expects the lambdafunction to throw only a specific set of exceptions. By providing an exceptionspec for your lambda function, you could allow a tool like PC Lint to checkthat for you. If you want to do that, it turns out you can. Here's a lambdathat specifies that it takes no arguments and does not throw an exception:

[] () throw () { /* code that you don't expect to throw an exception*/ }

How are Lambda Closures Implemented?

So how does the magic of variable capture really work? It turns out that theway lambdas are implemented is by creating a small class; this class overloadsthe operator(), so that it acts just like a function. A lambda function is aninstance of this class; when the class is constructed, any variables in thesurrounding enviroment are passed into the constructor of the lambda functionclass and saved as member variables. This is, in fact, quite a bit like theidea of a functorthat is already possible. The benefit of C++11 is that doing this becomesalmost trivially easy--so you can use it all the time, rather than only in veryrare circumstances where writing a whole new class makes sense.

C++, being very performance sensitive, actually gives you a ton offlexibility about what variables are captured, and how--all controlled via the capture specification, [].You've already seen two cases--with nothing in it, no variables are captured,and with &, variables are captured by reference. If you make a lambda withan empty capture group, [], rather than creating the class, C++ will create aregular function. Here's the full list of options:

[]Capture nothing (or, a scorched earth strategy?)
[&]Capture any referenced variable by reference
[=]Capture any referenced variable by making a copy
[=, &foo]Capture any referenced variable by making a copy, but capture variable foo by reference
[bar]Capture bar by making a copy; don't copy anything else
[this]Capture the this pointer of the enclosing class

Notice the last capture option--you don't need to include it if you'realready specifying a default capture (= or &), but the fact that you cancapture the this pointer of a function is super-important because it means thatyou don't need to make a distinction between local variables and fields of aclass when writing lambda functions. You can get access to both. The cool thingis that you don't need to explicitly use the this pointer; it's really like youare writing a function inline.

class Foo{public:    Foo () : _x( 3 ) {}    void func ()    {        // a very silly, but illustrative way of printing out the value of _x        [this] () { cout << _x; } ();    }private:        int _x;};int main(){    Foo f;    f.func();}

Dangers and Benefits of Capture by Reference

When you capture by reference, the lambda function is capable of modifying thelocal variable outside the lambda function--it is, after all, a reference. Butthis also means that if you return a lamba function from a function, youshouldn't use capture-by-reference because the reference will not be validafter the function returns.

What type is a Lambda?

The main reason that you'd want to create a lambda function is thatsomeone has created a function that expects to receive a lambda function. We'vealready seen that we can use templates to take a lambda function as anargument, and auto to hold onto a lambda function as a local variable. But howdo you name a specific lambda? Because each lambda function is implemented bycreating a separate class, as you saw earlier, even single lambda function isreally a different type--even if the two functions have the same arguments andthe same return value! But C++11 does include a convenient wrapper for storingany kind of function--lambda function, functor, or function pointer:std::function.

std::function

The new std::function is a great way of passing around lambda functions both asparameters and as return values. It allows you to specify the exact types forthe argument list and the return value in the template. Here's out AddressBookexample, this time using std::function instead of templates. Notice that we do need to use the 'functional' header file.

#include <functional>#include <vector>class AddressBook{    public:    std::vector<string> findMatchingAddresses (std::function<bool (const string&)> func)    {         std::vector<string> results;        for ( auto itr = _addresses.begin(), end = _addresses.end(); itr != end; ++itr )        {            // call the function passed into findMatchingAddresses and see if it matches            if ( func( *itr ) )            {                results.push_back( *itr );            }        }        return results;    }    private:    std::vector<string> _addresses;};

One big advantage of std::function over templates is that if you write a template, you need to put the whole function in the header file, whereas std::function does not. This can really help if you're working on code that will change a lot and is included by many source files.

If you want to check if a variable of type std::function is currently holding a valid function, you can always treat it like a boolean:

std::function<int ()> func;// check if we have a function (we don't since we didn't provide one)if ( func ) {    // if we did have a function, call it    func();}

A Note About Function Pointers

Under the final C++11 spec, if you have a lambda with an empty capturespecification, then it can be treated like a regular function and assigned to afunction pointer. Here's an example of using a function pointer with acapture-less lambda:

typedef int (*func)();func f = [] () -> int { return 2; };f();

This works because a lambda that doesn't have a capture group doesn't needits own class--it can be compiled to a regular old function, allowing it to bepassed around just like a normal function. Unfortunately, support for thisfeature is not included in MSVC 10, as it was added to the standard toolate.

Making Delegates with Lambdas

Let's look at one more example of a lambda function--this time to create adelegate. What's a delgate, you ask? When you call a normal function, all youneed is the function itself. When you call a method on an object, you need twothings: the function and the object itself. It's the difference between func()and obj.method(). To call a method, you need both. Just passing in the addressof the method into a function isn't enough; you need to have an object to callthe method on.

Let's look at an example, starting with some code that again expects afunction as an argument, into which we'll pass a delegate.

#include <functional>#include <string>class EmailProcessor{public:    void receiveMessage (const std::string& message)    {        if ( _handler_func )         {            _handler_func( message );        }        // other processing    }    void setHandlerFunc (std::function<void (const std::string&)> handler_func)    {        _handler_func = handler_func;    }private:        std::function<void (const std::string&)> _handler_func;};

This is a pretty standard pattern of allowing a callback function to beregistered with a class when something interesting happens.

But now let's say we want another class that is responsible for keepingtrack of the longest message received so far (why do you want to do this? Maybeyou are a bored sysadmin). Anyway, we might create a little class for this:

#include <string>class MessageSizeStore{    MessageSizeStore () : _max_size( 0 ) {}    void checkMessage (const std::string& message )     {        const int size = message.length();        if ( size > _max_size )        {            _max_size = size;        }    }    int getSize ()    {        return _max_size;    }private:    int _max_size;};

What if we want to have the method checkMessage called whenever amessage arrives? We can't just pass in checkMessage itself--it's a method, so it needs an object.

EmailProcessor processor;MessageSizeStore size_store;processor.setHandlerFunc( checkMessage ); // this won't work

We need some way of binding the variable size_store into the function passedto setHandlerFunc. Hmm, sounds like a job for lambda!

EmailProcessor processor;MessageSizeStore size_store;processor.setHandlerFunc(         [&] (const std::string& message) { size_store.checkMessage( message ); } );

Isn't that cool? We are using the lambda function here as glue code,allowing us to pass a regular function into setHandlerFunc, while still makinga call onto a method--creating a simple delegate in C++.

In Summary

So are lambda functions really going to start showing upall over the place in C++ code when the language has survived for decadeswithout them? I think so--I've started using lambda functions in productioncode, and they are starting to show up all over the place--in some casesshortening code, in some cases improving unittests, and in some cases replacing what could previously have only beenaccomplished with macros. So yeah, I think lambdas rock way more than anyother Greek letter.

Lambda functions are available in GCC 4.5 andlater, as well as MSVC 10 and version 11 of the Intel compiler.

Next: Range-Based For Loops Range-based for loops make iterating over vectors and other containers very easy
Previous: How auto, decltype, and the new function syntax work together to create better code Learn about some of the new type inference features of C++11
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C++11新特性:Lambda函数(匿名函数)
C++ function、bind以及lamda表达式
一些让人很容易忽视的shell基础知识
C#由变量捕获引起对闭包的思考
C++11新特性学习笔记
C 代码简化之道
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服