C++ lambda表达式的捕获原理

C++ Lambda表达式捕获机制的详细原理分析

Lambda的本质机制

Lambda表达式本质是编译器生成的匿名类,通过重载operator()实现函数调用运算符。捕获列表中的变量会被转换为该类的成员变量:

// 原始Lambda
auto f = [x](){ return x+1; };

// 等效编译器生成类
class __lambda_XXXX {
    int x;  // 捕获变量存储为成员变量
public:
    int operator()() const { return x+1; }
};

关键实现细节

  1. 捕获时机:发生在lambda表达式定义时,后续外部变量修改不影响已捕获的值(引用捕获除外)

  2. 成员初始化

    __lambda_XXXX{var} // 值捕获时调用拷贝构造
    __lambda_XXXX{std::move(var)} // C++14支持移动捕获
    
  3. 生命周期问题:引用捕获必须确保外部变量生命周期长于lambda对象

  4. 性能影响

    • 值捕获:可能引起拷贝开销
    • 引用捕获:无拷贝开销,但有悬空引用风险

特殊捕获方式

  1. 初始化捕获(C++14)

    [ptr = std::move(unique_ptr)]{} // 移动语义捕获
    
  2. 结构化绑定捕获(C++17):

    auto [a, b] = getPair();
    auto f = [a, b](){...}; 
    

[ ] 不截取任何变量
[&]截取外部作用域中所有变量,并作为引用在函数体中使用
[=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用
[=,&variable]   截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对以逗号分隔variable使用引用
[&,variable] 以引用的方式捕获外部作用域中所有变量,对以逗号分隔的变量列表variable使用值的方式捕获
[variable] 对以逗号分隔的变量列表variable使用值的方式捕获
[&variable] 对以逗号分隔的变量列表variable使用引用的方式捕获
[this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。

cppinsights将高级C++代码转换为其等效的低级代码表示;

[ ]空捕获

  • 原理:不生成任何成员变量,相当于没有状态的函数对象
  • 限制:只能访问全局变量和静态变量

[&]全引用捕获

  • 原理:所有捕获变量存储为引用类型成员

  • 底层实现:

    class __lambda_7_11 {
        std::string& s; // 引用类型成员
        int& a;         // 引用类型成员
    public:
        void operator()() const {
            s += std::to_string(a);
            a += 100; // 直接修改原变量
        }
    };
    
  • 特性:修改会影响外部变量,需注意生命周期问题

源:

#include <string>
#include <iostream>
int main()
{
	int a = 10;
	std::string s = "hello";
	auto f = [&]()
		{
			s += std::to_string(a);
			a += 100;
		};
	std::cout << a << std::endl;

	f();
	return 0;
}

cppinsights展开:

#include <string>
#include <iostream>
int main()
{
	int a = 10;
	std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());

	class __lambda_7_11
	{
	public:
		inline /*constexpr */ void operator()() const
		{
			s.operator+=(std::to_string(a));
			a = a + 100;
		}

	private:
		std::basic_string<char>& s;
		int& a;

	public:
		__lambda_7_11(std::basic_string<char>& _s, int& _a)
			: s{ _s }, a{ _a }
		{
		}
	};  // 函数对象

	__lambda_7_11 f = __lambda_7_11{ s, a };          // 构造函数
	std::cout.operator<<(a).operator<<(std::endl);  // 输出
	f.operator()();                                 // 调用函数
	return 0;
}

[=]全值(拷贝)捕获

  • 原理:所有变量存储为值类型成员

  • 底层实现:

    class __lambda_9_11 {
        std::string s;  // 值类型成员
        int a;          // 值类型成员 
    public:
        void operator()() const { // 默认const修饰
            std::cout << s << a;  // 只能读取不能修改
        }
    };
    
  • mutable修饰:移除operator()的const限定,允许修改副本

    mutable lambda允许:
    void operator()() { // 非const版本
        a += 100; // 修改副本值
    }
    

源:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::string s = "hello";
	//  默认是常函数void operator()() const
	auto f = [=]() {
		std::cout << "Lambda = " << s << std::endl;
		std::cout << "Lambda = " << a << std::endl;
		// 报错:表达式必须是可修改的左值
		// a += 100;
	};
	// 捕获时机:发生在lambda表达式定义时,后续外部变量修改不影响已捕获的值(引用捕获除外)
	a = 100;
	std::cout << "a = " << a << std::endl;

	f();

	// 设置mutable,使得void operator()()可以修改捕获的变量的副本(非本身)
	auto f2 = [=]()mutable {
		std::cout << "Lambda = " << s << std::endl;
		std::cout << "Lambda = " << a << std::endl;
		// 这里的a是外部变量的副本
		s += std::to_string(a); // 无法改变外部变量s
		a += 100;				// 可以改变外部变量a
		std::cout << "Lambda = " << s << std::endl;
		std::cout << "Lambda = " << a << std::endl;
	};
	
	f2();

	// 没有改变外部变量
	std::cout << "s = " << s << std::endl;// s = hello 
	std::cout << "a = " << a << std::endl;// a = 10
	return 0;
}
/*
a = 100
Lambda = hello
Lambda = 10
Lambda = hello
Lambda = 100
Lambda = hello100
Lambda = 200
s = hello
a = 100
*/

cppinsights展开:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());

	class __lambda_9_11
	{
	public:
		inline /*constexpr */ void operator()() const
		{
			std::operator<<(std::operator<<(std::cout, "Lambda = "), s).operator<<(std::endl);
			std::operator<<(std::cout, "Lambda = ").operator<<(a).operator<<(std::endl);
		}

	private:
		std::basic_string<char> s;
		int a;
	public:
		// inline __lambda_9_11 & operator=(const __lambda_9_11 &) /* noexcept */ = delete;
		// inline ~__lambda_9_11() noexcept = default;
		__lambda_9_11(const std::basic_string<char>& _s, int& _a)
			: s{ _s }
			, a{ _a }
		{}

	};

	__lambda_9_11 f = __lambda_9_11{ s, a };
	// 捕获时机:发生在lambda表达式定义时,后续外部变量修改不影响已捕获的值(引用捕获除外)
	a = 100;
	std::operator<<(std::cout, "a = ").operator<<(a).operator<<(std::endl);
	f.operator()();

	class __lambda_22_12
	{
	public:
		inline /*constexpr */ void operator()()
		{
			std::operator<<(std::operator<<(std::cout, "Lambda = "), s).operator<<(std::endl);
			std::operator<<(std::cout, "Lambda = ").operator<<(a).operator<<(std::endl);
			s.operator+=(std::to_string(a));
			a = a + 100;
			std::operator<<(std::operator<<(std::cout, "Lambda = "), s).operator<<(std::endl);
			std::operator<<(std::cout, "Lambda = ").operator<<(a).operator<<(std::endl);
		}

	private:
		std::basic_string<char> s;
		int a;
	public:
		// inline __lambda_22_12 & operator=(const __lambda_22_12 &) /* noexcept */ = delete;
		// inline ~__lambda_22_12() noexcept = default;
		__lambda_22_12(const std::basic_string<char>& _s, int& _a)
			: s{ _s }
			, a{ _a }
		{}

	};

	__lambda_22_12 f2 = __lambda_22_12{ s, a };
	f2.operator()();
	std::operator<<(std::operator<<(std::cout, "s = "), s).operator<<(std::endl);
	std::operator<<(std::cout, "a = ").operator<<(a).operator<<(std::endl);
	return 0;
}

混合捕获

形式原理成员变量类型
[=, &var]除var外全值捕获,var引用捕获值类型成员 + var引用成员
[&, var]除var外全引用捕获,var值捕获引用类型成员 + var值成员
[var1, &var2]显式指定值/引用捕获var1值成员 + var2引用成员

示例混合捕获展开代码:

// [=,&s] 捕获
class __lambda_9_11 {
    std::string& s; // 引用捕获
    int a;          // 值捕获
};

[=,&variable]拷贝及部分引用捕获

源:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::string s = "hello";
	//  默认是常函数void operator()() const
	auto f = [=,&s]() {
		s += std::to_string(a);
		std::cout << a << std::endl;
		// 报错:表达式必须是可修改的左值
		// a += 100;
	};
	std::cout << s << std::endl;

	f();

	std::cout << s << std::endl;	// hello10
         return 0;
}

cppinsights展开:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());

	class __lambda_9_11
	{
	public:
		inline /*constexpr */ void operator()() const
		{
			s.operator+=(std::to_string(a));
			std::cout.operator<<(a).operator<<(std::endl);
		}

	private:
		std::basic_string<char>& s;
		int a;

	public:
		__lambda_9_11(std::basic_string<char>& _s, int& _a)
			: s{ _s }, a{ _a }
		{
		}
	};

	__lambda_9_11 f = __lambda_9_11{ s, a };
	std::operator<<(std::cout, s).operator<<(std::endl);
	f.operator()();
	std::operator<<(std::cout, s).operator<<(std::endl);
	return 0;
}

[&,variable]引用及部分拷贝捕获

源:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::string s = "hello";
	//  默认是常函数void operator()() const
	auto f = [&,a]() {
		s += std::to_string(a);
		std::cout << a << std::endl;
		// 报错:表达式必须是可修改的左值;“a” : 无法在非可变 lambda 中修改通过复制捕获
		// a += 100;
	};
	std::cout << s << std::endl;	// hello

	f();

	std::cout << s << std::endl;	// hello10
         return 0;
}

cppinsights展开:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());

	class __lambda_9_11
	{
	public:
		inline /*constexpr */ void operator()() const
		{
			s.operator+=(std::to_string(a));
			std::cout.operator<<(a).operator<<(std::endl);
		}

	private:
		int a;
		std::basic_string<char>& s;

	public:
		__lambda_9_11(int& _a, std::basic_string<char>& _s)
			: a{ _a }
			, s{ _s }
		{}

	};

	__lambda_9_11 f = __lambda_9_11{ a, s };
	std::operator<<(std::cout, s).operator<<(std::endl);
	f.operator()();
	std::operator<<(std::cout, s).operator<<(std::endl);
	return 0;
}

显式捕获

  • 值捕获

    [var]
    
    • 生成const成员变量
    • 需mutable才能修改副本,修改不影响外部变量
  • 引用捕获

    [&var]
    
    • 生成引用类型成员
    • 修改直接影响外部变量

[variable]拷贝捕获部分变量

源:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::string s = "hello";
	//  默认是常函数void operator()() const
	auto f = [a]() {
		// 报错:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中
		// std::cout << s << std::endl;
		
		std::cout << a << std::endl;
		// 报错:表达式必须是可修改的左值;“a” : 无法在非可变 lambda 中修改通过复制捕获
		// a += 100;
	};
	std::cout << s << std::endl;	// hello

	f();
         return 0;
}

cppinsights展开:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());

	class __lambda_9_11
	{
	public:
		inline /*constexpr */ void operator()() const
		{
			std::cout.operator<<(a).operator<<(std::endl);
		}

	private:
		int a;

	public:
		__lambda_9_11(int& _a)
			: a{ _a }
		{}

	};

	__lambda_9_11 f = __lambda_9_11{ a };
	std::operator<<(std::cout, s).operator<<(std::endl);
	f.operator()();
	return 0;
}

[&variable]引用捕获部分变量

源:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::string s = "hello";
	//  默认是常函数void operator()() const
	auto f = [&a]() {
		// 报错:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中
		// std::cout << s << std::endl;
		
		a += 100;
	};
	std::cout << a << std::endl;		// 10

	f();

	std::cout << a << std::endl;		// 110
         return 0; 
}

cppinsights展开:

#include <string>
#include <iostream>

int main()
{
	int a = 10;
	std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());

	class __lambda_9_11
	{
	public:
		inline /*constexpr */ void operator()() const
		{
			a = a + 100;
		}

	private:
		int& a;

	public:
		__lambda_9_11(int& _a)
			: a{ _a }
		{}

	};

	__lambda_9_11 f = __lambda_9_11{ a };
	std::cout.operator<<(a).operator<<(std::endl);
	f.operator()();
	std::cout.operator<<(a).operator<<(std::endl);
	return 0;
}

[this]拷贝捕获this

  • 原理:捕获当前对象的this指针

  • 实现方式:

    class __lambda_13_12 {
        MyClass* __this; // 存储this指针
    public:
        void operator()() const {
            __this->a += 100; // 访问成员变量
        }
    };
    
  • 注意:当使用[=][&]时会隐式捕获this

源:

#include <string>
#include <iostream>

class MyClass
{
public:
	MyClass() = default;
	~MyClass() = default;

	void lambdaCapture()
	{
		//  默认是常函数void operator()() const
		auto f = [this]() {
			// 报错:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中
			// std::cout << s << std::endl;

			a += 100;
		};
		std::cout << a << std::endl;		// 10

		f();

		std::cout << a << std::endl;		// 110
	}
private:
	int a = 10;
	std::string s = "hello";
};

int main()
{
	MyClass().lambdaCapture();
         return 0;
}

cppinsights展开:

#include <string>
#include <iostream>

class MyClass
{

public:
	inline constexpr MyClass() noexcept(false) = default;
	inline ~MyClass() noexcept = default;
	inline void lambdaCapture()
	{

		class __lambda_13_12
		{
		public:
			inline /*constexpr */ void operator()() const
			{
				__this->a = __this->a + 100;
			}

		private:
			MyClass* __this;

		public:
			__lambda_13_12(MyClass* _this)
				: __this{ _this }
			{}

		};

		__lambda_13_12 f = __lambda_13_12{ this };
		std::cout.operator<<(this->a).operator<<(std::endl);
		f.operator()();
		std::cout.operator<<(this->a).operator<<(std::endl);
	}


private:
	int a;
	std::basic_string<char> s;
public:
};


int main()
{
	MyClass().lambdaCapture();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值