• 向 lambda 传递 this的拷贝


    lambda 传递 this的拷贝

    当在非静态成员函数里使用 lambda时,你不能隐式获取对该对象成员的使用权。也就是说,如果你不捕
    获this的话你将不能在 lambda里使用该对象的任何成员(即使你用 this>来访问也不行):
    
    • 1
    • 2
    class C {
    private:
    std::string name;
    public:
    ...
    void foo() {
    auto l1 = [] { std::cout << name << '\n'; }; // ERROR
    auto l2 = [] { std::cout << this>name << '\n'; }; // ERROR
    ...
    }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在C++11和 C++14里,你可以通过值或引用捕获 this:

    class C {
    private:
    std::string name;
    public:
    ...
    void foo() {
    auto l1 = [this] { std::cout << name << '\n'; }; // OK
    auto l2 = [=] { std::cout << name << '\n'; }; // OK
    auto l3 = [&] { std::cout << name << '\n'; }; // OK
    ...
    }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    向lambda传递 this的拷贝
    然而,问题是即使是用拷贝的方式捕获this实质上获得的也是引用(因为只会拷贝this指针)。当lambda的生
    命周期比该对象的生命周期更长的时候,调用这样的函数就可能导致问题。比如一个极端的例子是在 lambda中
    开启一个新的线程来完成某些任务, 调用新线程时正确的做法是传递整个对象的拷贝来避免并发和生存周期的
    问题,而不是传递该对象的引用。另外有时候你可能只是简单的想向 lambda传递当前对象的拷贝。

    自从 C++14 起有了一个解决方案,但可读性和实际效果都比较差:

    class C {
    private:
    std::string name;
    public:
    ...
    void foo() {
    auto l1 = [thisCopy=*this] { std::cout << thisCopy.name << '\n'; };
    ...
    }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    例如,当使用了 =或者 &捕获了其他对象的时候你可能会在不经意间使用 this:

    auto l1 = [&, thisCopy=*this] {
    thisCopy.name = "new name";
    std::cout << name << '\n'; // OOPS: 仍 然 使 用 了 原 来 的name
    };
    //自从 C++17 起,你可以通过 *this显式地捕获当前对象的拷贝:
    class C {
    private:
    std::string name;
    public:
    ...
    void foo() {
    auto l1 = [*this] { std::cout << name << '\n'; };
    ...
    }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这里,捕获 *this意味着该 lambda生成的闭包将存储当前对象的一份拷贝。
    你仍然可以在捕获 *this的同时捕获其他对象,只要没有多个 this的矛盾:

    auto l2 = [&, *this] { ... }; // OK
    auto l3 = [this, *this] { ... }; // ERROR
    
    • 1
    • 2

    这里有一个完整的例子:

    #include 
    #include 
    #include 
    
    class Data {
    private:
    std::string name;
    public:
    Data(const std::string& s) : name(s) {
    }
    auto startThreadWithCopyOfThis() const {
    // 开 启 并 返 回 新 线 程, 新 线 程 将 在3秒 后 使 用this:
    using namespace std::literals;
    std::thread t([*this] {
    std::this_thread::sleep_for(3s);
    std::cout << name << '\n';
    });
    return t;
    }
    };
    int main()
    {
    std::thread t;
    {
    Data d{"c1"};
    t = d.startThreadWithCopyOfThis();
    } // d不 再 有 效
    t.join();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    lambda里捕获了 *this,所以传递进 lambda的是一份拷贝。因此,即使在 d被销毁之后使用捕获的对象也没有
    问题。
    如果我们使用[this]、[=]或者 [&]捕获 this,那么新线程将会陷入未定义行为,因为当线程中打印name的
    时候将会使用一个已经销毁的对象的成员。

  • 相关阅读:
    Threejs入门教程
    如何下载MyBatis呢?
    Effective C++看书笔记(3):资源管理
    C/C++变长参数
    K8S管理工具-kubectl ①
    在汽车行业中如何脱颖而出?使用聊天机器人是关键
    SpringBoot添加外部jar包及打包(亲测有效) - 第452篇
    如何给在 SAP Business Application Studio 里开发的 OData 服务准备测试数据试读版
    【PXIE301-211】青翼科技基于PXIE总线的16路并行LVDS数据采集、1路光纤数据收发处理平台
    MySQL修改表时添加和删除约束
  • 原文地址:https://blog.csdn.net/weixin_42244181/article/details/126919327