lambda问题
本文最后更新于 924 天前,其中的信息可能已经有所发展或是发生改变。

C++一个坑。如果直接按值捕获外部对象,那么在lambda内部修改它,会被视为语法错误,不能通过编译。但是在Class成员函数中按值捕获外部所有的对象,将Class的this指针还有Class内部的成员变量一起捕获了,直接使用变量名访问成员变量时,会产生二义性,实际上访问的是lambda按值捕获的时候拷贝的哪一个,而不是this指向内存中的那个。原本正常情况下这样修改按值捕获的外部变量属于语法错误,但是这样特殊的情景下,错误的代码以一种错误的方法运行了起来,带来的错误的预期。
不要过分相信编译器。
又趟了一个坑


ChatGPT交流过程

S:std::function按引用传递和按值传递有什么不同?

ChatGPT:在C++中,std::function是一个通用的函数包装器,用于存储和调用可调用对象(函数、函数指针、函数对象等)。

当你使用std::function按引用传递一个可调用对象时,它会将该对象的引用存储在std::function中。这意味着在调用std::function时,它仍然引用原始的可调用对象。任何对std::function的调用都会直接调用原始的可调用对象。

当你使用std::function按值传递一个可调用对象时,它会复制该对象,并将复制的副本存储在std::function中。这意味着在调用std::function时,它将调用复制的可调用对象,而不会影响原始的可调用对象。

区别在于按引用传递会共享原始可调用对象的状态和修改,而按值传递会复制可调用对象并独立维护其状态。这意味着按引用传递可能对原始对象的修改产生影响,而按值传递则不会。

需要注意的是,按引用传递可能存在悬空引用(dangling reference)的问题,因为原始可调用对象可能在std::function调用之前被销毁。为了避免这个问题,确保在使用std::function时,原始可调用对象的生命周期足够长。

选择按引用传递或按值传递std::function取决于你对可调用对象的使用需求。如果你希望std::function能够反映原始对象的修改,并与原始对象共享状态,那么选择按引用传递。如果你希望std::function拥有独立的状态,并且对原始对象的修改不会影响std::function的行为,那么选择按值传递。

S:我使用lambda表达式,在一个函数内部将一个std::function对象按引用赋给一个变量,但是这个变量在第二次被调用时会触发段错误异常,是不是因为,按引用传递的std::function在离开作用域后被释放了?

ChatGPT:是的,你的猜测是正确的。当你将一个std::function对象按引用传递给另一个变量,并在该变量的作用域之外调用它时,会导致悬空引用的问题。当原始的std::function对象超出其作用域时,它被销毁,而按引用传递的变量将引用一个已经无效的对象,从而导致未定义的行为,可能触发段错误异常。

要避免这个问题,确保按引用传递的std::function对象的生命周期足够长,或者使用按值传递来复制std::function对象。这样可以保证在调用时,std::function对象仍然有效,并且不会引起悬空引用的问题。

以下是一个示例,演示了按引用传递std::function对象可能导致的问题:

#include <functional>
#include <iostream>

std::function<int(int)> createFunction()
{
    int value = 10;
    return [&](int x) { return value + x; };  // 按引用传递std::function对象
}

int main()
{
    std::function<int(int)> func = createFunction();
    int result = func(5);  // 调用std::function对象

    std::cout << result << std::endl;  // 输出 15

    result = func(10);  // 第二次调用std::function对象,可能导致段错误异常

    return 0;
}

在上述示例中,当第二次调用func时,由于createFunction函数返回的std::function对象已经超出了其作用域,按引用传递的变量value变成了悬空引用,导致未定义的行为。

S:我貌似不能按值传递。因为我的lambda表达式是在类的内部使用的,lambda按值捕获了class对象的this指针,它需要修改class对象的成员变量。在按值传递std::function时,lambda内部对class成员变量的修改,对于class来说不可见。根据你回答中关于“当你使用std::function按值传递一个可调用对象时,它会复制该对象,并将复制的副本存储在std::function中。这意味着在调用std::function时,它将调用复制的可调用对象,而不会影响原始的可调用对象。”的内容,我怀疑按值传递std::function时,它会创建一个新的class的对象,然后将它的thiz指针按值捕获给lambda,导致我在lambda中修改的成员变量是新的class实例中的,所以原本的class对象才无法感知到修改。不知道我的理解是否正确

ChatGPT:你的理解是正确的。当你按值传递一个std::function对象时,它会创建一个新的副本,包括其中捕获的所有变量。对于类对象,它会复制整个对象,包括成员变量和成员函数。在这种情况下,lambda表达式会按值捕获this指针,即复制了整个类对象。

当你在lambda内部修改类对象的成员变量时,实际上是修改了复制的对象,而不是原始的类对象。这是因为按值传递的std::function持有的是复制的副本,对复制的副本进行修改不会影响原始的类对象。

如果你希望修改原始的类对象,可以考虑使用按引用捕获this指针,或者使用其他方式来传递类对象的引用给std::function,以确保对原始对象的修改可见。

以下是一个示例,演示了按引用传递this指针以修改原始类对象的成员变量:

#include <functional>
#include <iostream>

class MyClass {
public:
    void modifyValue()
    {
        std::function<void()> func = [this]() {
            value = 20;  // 修改类对象的成员变量
        };

        func();  // 调用std::function对象

        std::cout << value << std::endl;  // 输出 20
    }

private:
    int value = 10;
};

int main()
{
    MyClass obj;
    obj.modifyValue();

    return 0;
}

在上述示例中,我们使用按引用捕获this指针,lambda表达式可以访问和修改原始的MyClass对象的成员变量。通过这种方式,你可以确保对原始对象的修改可见。

S:按值传递一个std::function对象,并且按值捕获class的this指针,导致它创建一个新的class实例时,是否不会调用Class的构造函数?我在Class的构造函数中添加输出语句,但是并没有得到我预期中的输出。

ChatGPT:当按值传递std::function对象并按值捕获类的this指针时,并不会创建新的类实例,因此不会调用类的构造函数。

在这种情况下,lambda表达式只会复制捕获的类实例,并将复制的副本存储在std::function对象中。这意味着复制的副本已经是一个完整的类对象,而不是通过构造函数创建的新实例。

因此,如果你在类的构造函数中添加了输出语句,而在按值捕获的lambda表达式中没有触发构造函数的调用,可能是因为复制的副本已经存在,并且不需要调用构造函数来创建新的实例。

下面是一个示例,演示了按值传递std::function对象并按值捕获this指针时,并没有调用类的构造函数:

#include <functional>
#include <iostream>

class MyClass {
public:
    MyClass()
    {
        std::cout << "Constructor called" << std::endl;
    }

    void modifyValue()
    {
        std::function<void()> func = [*this]() {
            value = 20;  // 修改类对象的成员变量
        };

        func();  // 调用std::function对象

        std::cout << value << std::endl;  // 输出 20
    }

private:
    int value = 10;
};

int main()
{
    MyClass obj;
    obj.modifyValue();

    return 0;
}

在上述示例中,当创建MyClass对象时,会调用构造函数并输出”Constructor called”。然后,在modifyValue函数中按值传递std::function对象并按值捕获this指针时,并没有再次调用构造函数,因为复制的副本已经存在。

请确保你在正确的位置添加了输出语句,以便在构造函数被调用时能够看到输出。

S:我在lambda表达式中直接使用=捕获所有对象,包括this指针。但是在lambda内部修改class中的非静态成员函数,对于创建这个lambda的对象来说不可见,class的构造函数并没有被调用,所以并没有新建实例。那么可能的原因是否是,使用=来捕获对象,将class中原本的对象,和this指针一并按值捕获了。然后由于我只写了变量名,而不是使用this->来访问成员变量,导致修改的是按值捕获中复制的数据,而不是class对象中的成员变量?

ChatGPT:是的,你的理解是正确的。当你在lambda表达式中使用=``捕获所有对象,包括this指针时,实际上是按值捕获了所有的对象,并且复制了它们的副本。这包括了类中的成员变量和this`指针。

在lambda内部修改类的非静态成员时,如果你只是使用变量名访问成员变量,而没有使用this->来明确指定访问的是类对象的成员变量,那么实际上修改的是按值捕获中复制的数据,而不是原始的类对象中的成员变量。

这是因为在lambda表达式中,成员变量被复制到了lambda的副本中,并且在lambda内部被视为局部变量。因此,如果你想修改原始的类对象的成员变量,需要使用this->来明确指定。

以下是一个示例,演示了在lambda内部使用=``捕获this指针后,如何使用this->来修改原始类对象的成员变量:

#include <iostream>

class MyClass {
public:
    void modifyValue()
    {
        auto lambda = [=]() {
            this->value = 20;  // 使用this->明确指定修改原始类对象的成员变量
        };

        lambda();

        std::cout << value << std::endl;  // 输出 20
    }

private:
    int value = 10;
};

int main()
{
    MyClass obj;
    obj.modifyValue();

    return 0;
}

在上述示例中,使用=``捕获this指针后,在lambda内部使用this->来访问和修改原始类对象的成员变量。这样,修改的就是原始类对象中的成员变量,而不是lambda的副本。

确保在lambda内部明确指定访问的是类对象的成员变量,而不是lambda的局部变量。


尝试复现

尝试复现失败了,我在拷贝的lambda中修改object内的成员变量,但是对class中可见。我觉得这应该是一个编译器的Bug。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇