现在的位置: 首页 > 综合 > 正文

C++ lambda表达式权威指南

2013年12月01日 ⁄ 综合 ⁄ 共 6567字 ⁄ 字号 评论关闭

        最近经常看到lambda表达式这几个字,有的人说它使得C++可以写出更加简单易懂的代码,也有人说它是语法糖,我就不纠结这个问题了,一个国外技术大师写了一篇文章来描述的,我就果断翻译了下(哥英语各种无语,四级3次才过,6级一直无缘,果断使用百度翻译加上自己的理解翻译的,不要吐槽哥,谢谢!),如果有些地方写得比较模糊,可以参考原文:http://www.cprogramming.com/c++11/c++11-lambda-closures.html

C++11标准中加入了lambda表达式特性,可能这个听起来很玄乎,其实就是很多语言里面的“闭包”而已。这是什么意思呢?lambda表达式可以在源代码中编写内联(通常传递到另一个函数,类似于函数指针和仿函数)。快速创造功能变得非常简洁大方,这不仅意味着你可以使用lambda表达式去除一些你以前必须单独命令功能的苦恼,也可以是你开始写一写依赖于创建快速和容易实现功能的能力。这篇文章中,我将首次解释为什么lambda是伟大的,其中的 一些例子,都是我测试使用学习的,你可以试着试一下,相信你会喜欢它。

想一下,如果你要实现一个地址薄的类,并且你希望能提供一个搜索功能。你可以提供一个简单的搜索函数,传一个字符串参数并返回一些列匹配这个字符串的地址薄。有时候你可能会想,我要搜索地址中含有某些字符的信息或者以什么开始的地址,或以什么结束的地址等。然我们来看看,如果提供这么一个通用的函数来搜索。

#include <iostream>
#include <vector>
#include <string>

class AddressBook
{
public:
	template <typename Func>
	std::vector<std::string> findMatchAddresses(Func func)
	{
		std::vector<std::string> results;
		for (auto iter = _addresses.begin(), end = _addresses.end(); iter != end; ++iter)
		{
			if (func(*iter))
			{
				results.push_back(*iter);
			}
		}
		return results;
	}

private:
	std::vector<std::string> _addresses;
};

任何人都可以通过这个函数来实现自己想的逻辑来完成查询操作,如果函数返回不为空,表示查询出来结果了,在早期的C++中,只能在其他地方定义一个函数,它不太方便的创建一个函数,这就是lambda表达式引进来的一个原因

基本lambda表达式语法

在你使用lambda表达式解决问题之前,先来看看一个最基本的lambda表达式使用方法。

#include <iostream>
int main(int argc, char* argv[])
{
	auto func = [] () { std::cout << "hello world"; };
	func();
	return 0;
}

好吧,你已经发现了lambda表达式,从[]开始,这个符号成为捕获规范它告诉我们正在创建一个lambda函数编译,你会发现每一个lamda表达式都有这样的开头表示(有些会稍有不同,后面会介绍到)。接下来,像其他任何函数一样,需要一个参数列表,哪里是返回值的描述呢?事实证明我们不需要,因为在C++11标准中,编译器可以推断lambda函数的返回值,它会做而不是强迫你加上它。下一行func()中,我们称之为lambda函数,它看起来就像调用其他任何函数一样,顺便说一下,这样来,你就不需要定义一个函数,然后用一个函数指针指向它。

举个例子,使用我们开始写的那个地址薄类

让我们看看我们如何应用于我们的地址薄类,首先创建一个简单的函数,找到电子邮件中包含.org的所有地址薄信息。

AddressBook global_address_book;
std::vector<std::string> findMatchAddresses()
{
	return global_address_book.findMatchAddresses( 
		[] (const std::string& addr) { return addr.find( ".org" ) != std::string::npos; } 
	);
}

我们再次看到里面使用的lambda表达式,从[]开始,这次里面有一个参数addr,我们测试其中是否包含.org,换句话说,遍历所有地址信息过程中,每次循环都会通过findMatchAddresses,返回所有满足条件的地址信息。

lambda变量捕获

虽然这样使用lambda表达式很cool,但是lambda表达式真正强大的秘密在于变量捕获,假定你要用上面的地址薄类来查找包含特定名称的地址,用下面这个代码区实现,是不是看起来更加nice!

// read in the name from a user, which we want to search
string 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; } 
);

事实证明,这样是完全合法的,这表明我们能够以一个在lambda之外声明一个变量,然后在lambda里面直接使用,在lambda函数在执行的时候,这些变量已经获取,当然这些需要告诉编译器,比如[]表示不会获取任何变量,而[&]告诉编译器执行变量捕获。是不是太神奇呢?我们可以创建一个lambda表达式实现逻辑,其中逻辑中用到的变量采取lambda变量捕获,这样写看起来是不是更加简洁,仅需要几行代码即可完成。在C++11标准中,我们可以有一个简单的接口地址薄,可以支持任何一种过滤,而且实现起来十分简单。只是为了好玩,让我们来实现这样一个功能:找到唯一的地址邮件地址是不是一定数目的字符长。

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

lambda表达式和STL

毫无疑问,一个lambda表达式的最大受益者是对标准库算法封装奠定基础。以前,使用类似于for_each是不可想象的,只能模拟,现在你可以使用for_each等STL算法几乎一样,如果你写一个正常的循环。比较如下: 

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

这是一段很cool的代码,如果你问我:它读取是结构话的,像一个正常的循环,但是你突然能够利用for_each提供了一个普通的for循环,例如保证你有权终止条件,现在你可能想,这样写会不会印象性能?嗯,重点就在这里,for_each拥有相同的性能,有时甚至比for循环更快。我希望你可以从这个例子看到lambda表达式和STL在一起的衣衣,lambda表达式不仅仅是一个更简单创建函数的方式,他们允许你以一种新的方式组织程序,在你的代码需要其他功能的时候,允许你抽象出来一个特定数据结构的处理逻辑。

新的lambda表达式

使用lambda表达式,参数列表,返回值等也是可选的,如果你想要一个函数,无参数,病无返回值,甚至都可以不添加逻辑,虽然是没有作用的,但是合法的,最短的lambda表达式:

[] {}

换一个更加令人折服的例子:

using namespace std;
#include <iostream>

int main()
{
    [] { cout << "Hello, my Greek friends"; }();
}

 就我个人而已,我不缺省参数列表,我认为[]()结构趋向于表明一个语义,使得lambda表达式更加nice,这个就不多说,标准会衡量。

返回值

默认情况下,lambda表达式不写返回语句,它缺省值为空,如果你有一个简单的返回表达式,编译器也会推断返回值的类型。

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

如果你写一个更复杂的lambda函数,与一个以上的返回值,你必须指定返回类型(一些编译器,如GCC,会让你摆脱这样做,即使你有一个以上的返回语句,单标准并不能保证它)。

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

lambda表达式的闭包实现

lambda表达式如何像魔术一样的真正工作呢?原来,它是通过创建一个小类,这个类重载operator,所以它的行为就像一个函数。lambda函数式这类的一个实例,当类的构造时,周围环境中的变量传递到lambda函数的构造函数中,并保存为成员变量。C++最敏感的就是性能,实际上给你大量的灵活性来实现变量的捕获,以至于所有控制通过捕获规范。你已经看到了两个案例没有用[]。其实这里的捕获标准很多,如下就是完整的列表:

[] 不捕获

[&] 以引用的方式捕获

[=] 通过变量的一个拷贝捕获

[=, &foo] 通过变量的拷贝捕获,但是用foo变量的引用捕获

[bar] 通过复制捕获,不要复制其他

[this] 捕获this指针对应成员

注意最后的一个,你不需要包括它,但是实际上,你可以捕获这个指针的成员,你不需要显示地使用这个指针,看起是是不是很nice!

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();
}

lambda表达式的应用方面

我们已经看到,使用模板以lambda作为参数,并自动保持在lambda函数作为一个局部变量,lambda函数式通过创建一个单独的类来实现的,正如你之前看到的,即使是单一的lambda函数,都是不同的类型,及时这两个函数具有相同的参数和返回值!但是C++11标准中,包括了存储任何类型的功能,即lambda函数,可以方便包装,或函数指针、std::function。

std::function

它是一个将lambda表达式作为参数非常优雅的一种方式,不仅包括传递参数的类型,也包括返回值。它允许你定义一个新的类型,继续给出开始使用的例子,不过这次用std::function作为参数。

#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;
};

这样最大的一个优点是比较通用和灵活,使用时,如下即可。

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();
}

关于函数指针

在最终的C++11标准制定上,如果你使用一个lambda表达式,并且没有捕获任何变量,那么它可以像一个普通的函数来处理和分配函数指针,如下所示:

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

这样做,只是为了使得lambda表达式可以定位到一般函数层次上,使其在某些情况下,可以与函数指针共同使用。


使用lambda实现委托
从下面代码就可以看出来了:

#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;
};

这是一个非常标准的写法,允许类注册一个回调函数,但是我们需要另一个类,负责跟踪哪一个最最久的,无论如何,我们可以创建下面一个小类:

#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;
};

如果我们希望checkMessage在你接收消息的时候执行,那么就可以使用下面的代码,将其注册为回调函数(委托),如果我们这样写代码:

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

它并不会向我们所设想的那么工作,我们需要这么实现:

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

是不是很酷?我们使用lambda来实现委托时一种享受,希望你会喜欢,如果不是特别清楚,可以测试一下。

总结

             现在,我在开发一些具体项目的时候就经常用到lambda函数,他们开始出现在我的艺术品之中,有些情况下是为了缩短代码使其简单优雅,有的是为了单元测试的一些情况,在某些情况下,可以用它取代之前用宏实现的功能。所以我觉得lambda表达式功能是强大的,在gcc4.5之后也支持lambda表达式,msvc10和11版本的编译器也支持。

http://blog.csdn.net/u010732473/article/details/9019759

抱歉!评论已关闭.