114 C++ lambda表达式捕获模式的陷阱分析和展示

一 捕获列表中的 &

捕获外部作用域中的所有变量,(不包括静态变量,静态变量不需要捕获),并作为引用在lambda表达式中使用

按照引用这种捕获方式,会导致lambda表达式包含绑定到局部变量的引用。

问题发生的点是这样:lambda表达式使用了外部的变量&,但是这个变量失效了。

问题代码

//我们还定义一个全局变量vector,这vector中的每一个元素都是function,且要求这个function的返回值是bool,参数是int

std::vector> g_vec;


void func138() {
	//调用这个,让每次产生的随机数都不一样。
	srand((unsigned)time(NULL));
	int suijishu = rand() % 6 + 1 ;//rand() % 6 会产生一个0-5的随机数,因此suijishu的值为1-6

	//我们在这个方法中给 g_vec存储一个数据
	g_vec.push_back([&](int tv) {
		if (tv%suijishu==0) {//如果suijishu是tv的整数倍
			return true;
		}
		return false;
	});
}

void main() {
	func138();//1.先调用func138函数,在这一句代码执行完毕后,已经给g_vec 塞一个数据。
	g_vec[0](10);//2.既然g_vec已经有了数据了,我们就能使用这个数据了,已知这个数据是 可调用对象,这个可调用对象就lambda表达式,需要一个int参数,返回一个bool

	//然后我们debug的时候发现,调用func138()函数的时候,suijizhi=5的,但是当执行g_vec[0](10)的时候,suijizhi变成乱码了
	//这是因为,当调用func138()函数的时候,suijizhi是用引用传递的,假设是5
	//当func138执行完毕的时候,这个suijizhi的生命周期就结束了,就不是5了。
	//因此当我们执行 g_vec[0](10)的时候,suijizhi就真的变成随机值了。不在是5了

}

简单类型可以使用的fix方案:使用值 替换 引用

但是这不是最终方案,我们这里可以使用 值替换,是因为该例子中,suijizhi是一个简单的int,那么如果是 指针呢?甚至更复杂的情况呢?在这些情况下,此方案不行

std::vector> g_vec;

void func139() {
	//调用这个,让每次产生的随机数都不一样。
	srand((unsigned)time(NULL));
	int suijishu = rand() % 6 + 1;//rand() % 6 会产生一个0-5的随机数,因此suijishu的值为1-6

	//我们在这个方法中给 g_vec存储一个数据
	g_vec.push_back([=](int tv) {
		if (tv%suijishu == 0) {//如果suijishu是tv的整数倍
			return true;
		}
		return false;
	});
}
void main() {
	func139();//1.先调用func139函数,在这一句代码执行完毕后,已经给g_vec 塞一个数据。
	g_vec[0](10);//2.既然g_vec已经有了数据了,我们就能使用这个数据了,已知这个数据是 可调用对象,这个可调用对象就lambda表达式,需要一个int参数,返回一个bool

	//然后我们debug的时候发现,调用func139()函数的时候,suijizhi=1的,且这个1这个值就直接进去到lambda表达式里面了
	//再次执行 g_vec[0](10)的时候,suijizhi的值还是1
}

二 形参列表可以使用 auto C++14 中允许 lambda表达式中的形参是auto 类型的,C++编译器可以自动推断,但是这个有啥用呢?好像也没用处。

注意,形参的地方写成auto 了。

void func139() {
	//调用这个,让每次产生的随机数都不一样。
	srand((unsigned)time(NULL));
	int suijishu = rand() % 6 + 1;//rand() % 6 会产生一个0-5的随机数,因此suijishu的值为1-6

	//我们在这个方法中给 g_vec存储一个数据
	g_vec.push_back([=](auto tv) {
		if (tv%suijishu == 0) {//如果suijishu是tv的整数倍
			return true;
		}
		return false;
	});
}

三 成员变量的捕获问题

问题现象,概念理清,问题原因

std::vector> g_vec;
//成员捕获问题

class Teacher140 {
public:
	int m_tempvalue = 5;
	void addItem() {
		//这里中括号里面写的是=,按照我们之前的学习,会认为这里是 按值捕获的m_tempvalue
		//实际上,这里有个概念是错误的, =
		// 等号相当于 this,
		g_vec.push_back([=](auto val) {
			cout << "m_tempvalue = " << m_tempvalue <<  endl;
			if (val % m_tempvalue == 0) {
				return true;
			}
			return false;
		});
	}
};
int mmage = 90;

void main() {
	//如下是正常的调用
	//Teacher140 *ptea = new Teacher140;
	//ptea->addItem();
	//cout << g_vec[0](10) << endl;
	//delete ptea;

	//
	//按照我们之前的理解, =是值copy,因此只要执行了ptea->addItem();m_tempvalue的值5,就会有一份copy到了lambda表达式中
	//因此delete pate 和 g_vec[0](10) 那个先执行无所谓,试一试
	Teacher140 *ptea1 = new Teacher140;
	ptea1->addItem();
	
	delete ptea1;
	cout << g_vec[0](10) << endl;
	//结果如下:
	//m_tempvalue = -572662307
	//0

	//先写结论:
	//lambda表达式的执行正确与否,取决于ptea1 对象是否存在,只有ptea1对象存在,这个lambda表达式才正确。
	
	//纠正一些概念:以及问题点分析
	//捕获这种概念,只针对于在创建lambda表达式的作用域内可见的非静态的局部变量,包括形参
	//那么m_tempvalue 符合上述定义吗?显然不符合,m_tempvalue是Teacher140的成员变量呀。
	//也就是说:我们实际上无法捕获到m_tempvalue的。因此也不存在值copy一份到lambda表达式的说法。

	//实际上 这个 等号,捕获的是this指针的值,由于有了this指针,m_tempvalue才可以访问。也就是说所有的成员变量 都是依赖于 this指针访问的,m_tempvalue 
	//那么这样就能理通了,捕获的是this,也就是说还是依赖于 ptea1,那么将ptea1 delete了,再次执行g_vec[0](10)的时候,由于ptea1不存在了,m_tempvalue也就不存在了,问题发生

}

解决方案

好,我们现在知道了问题的原因了,那么怎么改动呢?

当前case下,是有办法的,既然问题中的 成员变量捕获不到,我们让它能捕获到不就好了吗?

那么什么能被捕获呢?显然,局部变量就行,添加一个局部变量mcopyvalue ,将m_tempvalue的值赋值给 mcopyvalue,就OK了

int mcopyvalue = m_tempvalue;

注意的是:当前case 是一个int ,如果是个指针,或者一个其他类类型呢?写的要复杂的多。还有可能是不行的,比如指针,仅仅copy指针的值,肯定是不行的,内存也要copy。内存copy 了放在哪里呢?

std::vector> g_vec;
//成员捕获问题

class Teacher140 {
public:
	int m_tempvalue = 5;
	//void addItem() {
	//	//这里中括号里面写的是=,按照我们之前的学习,会认为这里是 按值捕获的m_tempvalue
	//	//实际上,这里有个概念是错误的, =
	//	// 等号相当于 this,
	//	g_vec.push_back([=](auto val) {
	//		cout << "m_tempvalue = " << m_tempvalue <<  endl;
	//		if (val % m_tempvalue == 0) {
	//			return true;
	//		}
	//		return false;
	//	});
	//}

	void addItem() {
		int mcopyvalue = m_tempvalue;
		g_vec.push_back([mcopyvalue](auto val) {
			cout << "mcopyvalue = " << mcopyvalue << endl;
			if (val % mcopyvalue == 0) {
				return true;
			}
			return false;
		});
	}
};

void main() {
	//如下是正常的调用
	//Teacher140 *ptea = new Teacher140;
	//ptea->addItem();
	//cout << g_vec[0](10) << endl;
	//delete ptea;

	//
	//按照我们之前的理解, =是值copy,因此只要执行了ptea->addItem();m_tempvalue的值5,就会有一份copy到了lambda表达式中
	//因此delete pate 和 g_vec[0](10) 那个先执行无所谓,试一试
	Teacher140 *ptea1 = new Teacher140;
	ptea1->addItem();
	
	delete ptea1;
	cout << g_vec[0](10) << endl;
	//结果如下:
	//m_tempvalue = -572662307
	//0

	//先写结论:
	//lambda表达式的执行正确与否,取决于ptea1 对象是否存在,只有ptea1对象存在,这个lambda表达式才正确。
	
	//纠正一些概念:以及问题点分析
	//捕获这种概念,只针对于在创建lambda表达式的作用域内可见的非静态的局部变量,包括形参
	//那么m_tempvalue 符合上述定义吗?显然不符合,m_tempvalue是Teacher140的成员变量呀。
	//也就是说:我们实际上无法捕获到m_tempvalue的。因此也不存在值copy一份到lambda表达式的说法。

	//实际上 这个 等号,捕获的是this指针的值,由于有了this指针,m_tempvalue才可以访问。也就是说所有的成员变量 都是依赖于 this指针访问的,m_tempvalue 
	//那么这样就能理通了,捕获的是this,也就是说还是依赖于 ptea1,那么将ptea1 delete了,再次执行g_vec[0](10)的时候,由于ptea1不存在了,m_tempvalue也就不存在了,问题发生

}

四。广义lambda 捕获

在C++14提出了 广义 lambda 捕获,实际上就是解决上面这个问题的,而且写法更简单,

目前没有测试过,是否能解决上述提出的疑问,就是如果不是int这种基本类型,是指针什么的,这个广义 lambda捕获是否能fix

核心就红色部分

void addItem() {
        g_vec.push_back([abc = m_tempvalue](auto val) {
            cout << "abc = " << abc << endl;
            if (val % abc == 0) {
                return true;
            }
            return false;
        });
    }

std::vector> g_vec;
//成员捕获问题

class Teacher140 {
public:
	int m_tempvalue = 5;
	//void addItem() {
	//	//这里中括号里面写的是=,按照我们之前的学习,会认为这里是 按值捕获的m_tempvalue
	//	//实际上,这里有个概念是错误的, =
	//	// 等号相当于 this,
	//	g_vec.push_back([=](auto val) {
	//		cout << "m_tempvalue = " << m_tempvalue <<  endl;
	//		if (val % m_tempvalue == 0) {
	//			return true;
	//		}
	//		return false;
	//	});
	//}

	//void addItem() {
	//	int mcopyvalue = m_tempvalue;
	//	g_vec.push_back([mcopyvalue](auto val) {
	//		cout << "mcopyvalue = " << mcopyvalue << endl;
	//		if (val % mcopyvalue == 0) {
	//			return true;
	//		}
	//		return false;
	//	});
	//}

	//C++14提出的广义lambda 捕获方案
	void addItem() {
		g_vec.push_back([abc = m_tempvalue](auto val) {
			cout << "abc = " << abc << endl;
			if (val % abc == 0) {
				return true;
			}
			return false;
		});
	}
};

void main() {
	//如下是正常的调用
	//Teacher140 *ptea = new Teacher140;
	//ptea->addItem();
	//cout << g_vec[0](10) << endl;
	//delete ptea;

	//
	//按照我们之前的理解, =是值copy,因此只要执行了ptea->addItem();m_tempvalue的值5,就会有一份copy到了lambda表达式中
	//因此delete pate 和 g_vec[0](10) 那个先执行无所谓,试一试
	Teacher140 *ptea1 = new Teacher140;
	ptea1->addItem();
	
	delete ptea1;
	cout << g_vec[0](10) << endl;
	//结果如下:
	//m_tempvalue = -572662307
	//0

	//先写结论:
	//lambda表达式的执行正确与否,取决于ptea1 对象是否存在,只有ptea1对象存在,这个lambda表达式才正确。
	
	//纠正一些概念:以及问题点分析
	//捕获这种概念,只针对于在创建lambda表达式的作用域内可见的非静态的局部变量,包括形参
	//那么m_tempvalue 符合上述定义吗?显然不符合,m_tempvalue是Teacher140的成员变量呀。
	//也就是说:我们实际上无法捕获到m_tempvalue的。因此也不存在值copy一份到lambda表达式的说法。

	//实际上 这个 等号,捕获的是this指针的值,由于有了this指针,m_tempvalue才可以访问。也就是说所有的成员变量 都是依赖于 this指针访问的,m_tempvalue 
	//那么这样就能理通了,捕获的是this,也就是说还是依赖于 ptea1,那么将ptea1 delete了,再次执行g_vec[0](10)的时候,由于ptea1不存在了,m_tempvalue也就不存在了,问题发生

}

五。静态局部变量

静态局部变量是不需要捕获的,由于在静态存储区放着,可以在lambda表达式中使用。

静态局部变量是保存在 静态存储区,它的有效期一直到程序结束。

静态局部变量的使用有点类似于按 引用 捕获这种效果。

你可能感兴趣的:(c++,开发语言)