C++17 新特性解析:Lambda 捕获 this

打印 上一主题 下一主题

主题 1045|帖子 1045|积分 3135


C++17 引入了许多改进和新特性,其中之一是对 lambda 表达式的增强。在这篇文章中,我们将深入探讨 lambda 表达式中的一个特殊有效的新特性:通过 *this 捕获当前对象的副本。这个特性不仅进步了代码的安全性,还极大地简化了某些场景下的编程模式。

  • Lambda 表达式简介
Lambda 表达式是 C++11 中首次引入的一种匿名函数对象,它极大地简化了编程模式,特殊是在使用 STL 算法或进行变乱驱动编程时。Lambda 表达式的基本语法如下:
  1. [捕获列表](参数列表) -> 返回类型 {
  2.     函数体
  3. };
复制代码


  • 捕获列表:用于捕获外部变量,使其在 lambda 表达式中可用。
  • 参数列表:与普通函数雷同,用于接收参数。
  • 返回范例:可选,用于指定 lambda 的返回范例。
  • 函数体:包罗 lambda 的逻辑。
比方,以下是一个简朴的 lambda 表达式,用于打印一个整数:
  1. auto print = [](int x) {
  2.     std::cout << x << std::endl;
  3. };
  4. print(42);
复制代码
Lambda 表达式的强大之处在于它的机动性和简洁性,它允许我们在需要的地方快速定义一个匿名函数,而无需单独声明一个函数对象。

  • C++17 中的 *this 捕获
在 C++17 之前,假如你想在 lambda 表达式中使用当前类的成员变量或成员函数,你通常会捕获 this 指针。比方:
  1. class MyClass {
  2. public:
  3.     int value = 10;
  4.     void doSomething() {
  5.         auto lambda = [this]() {
  6.             std::cout << this->value << std::endl;
  7.         };
  8.         lambda();
  9.     }
  10. };
复制代码
这种方式的问题是,它捕获的是 this 指针,而不是对象自己。这意味着假如外部对象的生命周期竣事,而 lambda 表达式仍在使用,就可能访问到无效的内存。这种问题在多线程或异步编程中尤为常见,可能导致难以调试的错误。
为了解决这个问题,C++17 引入了通过 *this 捕获当前对象的副本的本事。如许,lambda 表达式就拥有了当前对象的一个完整副本,从而避免了潜伏的悬挂指针问题。
示例代码
  1. class MyClass {
  2. public:
  3.     int value = 10;
  4.     void doSomething() {
  5.         auto lambda = [*this]() {
  6.             std::cout << value << std::endl; // 直接使用 value,不需要 this-> 前缀
  7.         };
  8.         lambda();
  9.     }
  10. };
复制代码
在这个例子中,*this 在 lambda 表达式中创建了 MyClass 的一个副本,因此即使原始对象被销毁,lambda 表达式中的副本仍然是有效的。这种捕获方式不仅安全,还简化了代码的编写。

  • 使用场景
*this 的捕获非常恰当以下几种场景:
3.1 异步操纵
在多线程或异步编程中,lambda 表达式可能会在差别的线程中实验。假如捕获的是 this 指针,而原始对象的生命周期竣事,可能会导致未定义行为。通过捕获 *this,可以确保 lambda 表达式中使用的对象副本始终有效。
  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. class MyClass {
  5. public:
  6.     int value = 10;
  7.     void doSomething() {
  8.         auto lambda = [*this]() {
  9.             std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟异步操作
  10.             std::cout << value << std::endl;
  11.         };
  12.         // 启动异步任务
  13.         std::async(std::launch::async, lambda);
  14.     }
  15. };
  16. int main() {
  17.     MyClass obj;
  18.     obj.doSomething();
  19.     return 0; // 对象 obj 的生命周期结束,但 lambda 中的副本仍然有效
  20. }
复制代码
3.2 避免悬挂指针
当你担心原始对象可能在 lambda 实验时被销毁,使用 *this 捕获可以安全地复制对象,避免访问已销毁的对象。
  1. class MyClass {
  2. public:
  3.     int value = 10;
  4.     void doSomething() {
  5.         auto lambda = [*this]() {
  6.             std::cout << value << std::endl;
  7.         };
  8.         lambda();
  9.     }
  10. };
  11. int main() {
  12.     MyClass obj;
  13.     auto lambda = obj.doSomething();
  14.     // obj 的生命周期结束,但 lambda 中的副本仍然有效
  15. }
复制代码
3.3 值捕获的简化
直接通过 *this 捕获可以避免列出类中每个需要的成员。假如你的类中有多个成员变量或成员函数需要在 lambda 中使用,捕获 *this 是一种更简洁的方式。
  1. class MyClass {
  2. public:
  3.     int value1 = 10;
  4.     int value2 = 20;
  5.     void doSomething() {
  6.         auto lambda = [*this]() {
  7.             std::cout << value1 + value2 << std::endl;
  8.         };
  9.         lambda();
  10.     }
  11. };
复制代码

  • 性能与留意事项
固然 *this 捕获提供了极大的便利和安全性,但它也引入了一些性能开销。捕获 *this 会创建当前对象的一个副本,这意味着:


  • 对象的拷贝构造函数会被调用。假如对象较大或拷贝构造函数较复杂,可能会导致性能降落。
  • 内存占用增长。由于 lambda 中存储了对象的副本,因此需要更多的内存。
因此,在使用 *this 捕获时,需要权衡安全性和性能。假如对象较小且拷贝构造函数简朴,*this 捕获是一个非常好的选择。但 if 对象较大或拷贝操纵代价较高,可能需要考虑其他方式,比方手动管理对象的生命周期或使用智能指针。

  • 总结
C++17 的 *this 捕获为 lambda 表达式提供了更大的机动性和安全性。通过允许复制当前对象,它不仅简化了代码,还增强了程序的结实性。这是 C++17 中浩繁改进中的一个亮点,值得每个 C++ 开辟者了解和使用。
在实际开辟中,公道使用 *this 捕获可以避免悬挂指针问题,简化异步编程的复杂性,并进步代码的可读性和安全性。固然,开辟者也需要根据实际环境权衡性能和安全性,选择最恰当的捕获方式。
希望这篇文章能帮助你更好地理解和使用 C++17 中的这一新特性。假如你有任何问题或发起,接待在评论区留言讨论!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表