简体   繁体   English

Lambda函数捕获错误的“ this”指针

[英]Lambda function captures wrong “this” pointer

I'm having a problem with making one trivial C++ project involving lambda-expressions and shared pointers to work. 我在制作一个包含lambda表达式和共享工作指针的普通C ++项目时遇到了问题。 The project is in Visual Studio, Debug, x64. 该项目在Visual Studio,Debug,x64中。 This is the code I have. 这是我的代码。

Class1.h: Class1.h:

#pragma once
#include <functional>

class Class1
{
    int m_data;
    const int* m_data_ptr;
public:
    std::function<int(int)> m_func;
    Class1(int, int);
    void Assign(const int&);
};

Class2.h: Class2.h:

#pragma once
#include "Class1.h"

class Class2
{
    Class1 m_class1obj;
public:
    Class2(int, int);
    void Assign(const int&);
    int Compute(int);
};

main.cpp: main.cpp:

#include <iostream>
#include "Class1.h"
#include "Class2.h"

Class1::Class1(int i, int j) : m_data(j), m_data_ptr(nullptr)
{
    m_func = [i, this](int x)
    {
        int val = *m_data_ptr;
        return (val + m_data + i)*x;
    };
    std::cout << "Creating class1 object!\n";
}
void Class1::Assign(const int& v)
{
    m_data_ptr = &v;
}
Class2::Class2(int i, int j) : m_class1obj(i, j)
{
    std::cout << "Creating class2 object!\n";
}
void Class2::Assign(const int& v)
{
    m_class1obj.Assign(v);
}
int Class2::Compute(int v)
{
    return m_class1obj.m_func(v);
}
int main()
{
    int val = 4;
    /*
    Class2 class2obj(3, 5);
    class2obj.Assign(val);
    std::cout << class2obj.Compute(23.0) << std::endl;
    */
    std::shared_ptr<Class2> class2_ptr;
    class2_ptr = std::make_shared<Class2>(Class2(3, 5));
    class2_ptr->Assign(val);
    std::cout << class2_ptr->Compute(23) << std::endl;
}

The code compiles fine, but crashes while executing the last line of main() . 代码可以正常编译,但是在执行main()的最后一行时会崩溃。 While debugging, I found that the problem discovers itself upon completion of the line class2_ptr = std::make_shared<Class2>(Class2(3, 5)); 在调试时,我发现问题是在完成class2_ptr = std::make_shared<Class2>(Class2(3, 5)); For some reason, when lambda in line m_func = ... captures the this pointer at Class1 object's construction time, it records the address that becomes different from the object's address right after the smart pointer for Class2 has been created! 出于某种原因,当m_func = ...行中的lambda在Class1对象的构造时间捕获this指针时,它会在创建Class2的智能指针后立即记录与该对象的地址不同的地址! It seems the first recorded address becomes outdated. 似乎第一个记录的地址已过时。 When I call class2_ptr->Compute(23) , I end up dereferencing a null pointer at int val = *m_data_ptr; 当我调用class2_ptr->Compute(23) ,我最终在int val = *m_data_ptr;处取消了对空指针的引用int val = *m_data_ptr; , thus causing the crash, even though I assigned a non-null address in class2_ptr->Assign(val); ,即使我在class2_ptr->Assign(val);分配了一个非空地址,也因此导致崩溃class2_ptr->Assign(val); prior to calling m_func ! 在调用m_func之前! But why did the address of this change? 但是为什么地址this变化? It is because of internal reallocation of the object in memory hidden from a user? 是因为对象的内部重新分配对用户隐藏了吗? If so, why didn't the compiler reassign the proper value of this in the storage of m_func ? 如果是这样,为什么没有编译器重新分配的正确值this在存储m_func Also, despite having the wrong address of the object of Class1 recored in m_func , the other data member m_data is accessed with correct value in m_func . 同样,尽管在m_func记录了Class1对象的错误地址,但仍使用m_data中的正确值访问了其他数据成员m_func

An important thing is that if I comment out the last four lines in main() and remove comments for the other three lines, the program does not crash! 重要的是,如果我注释掉main()的最后四行并删除其他三行的注释,则程序不会崩溃! Clearly, using shared pointer has something to do with it. 显然,使用共享指针与它有关。 Is there a problem in improper use of lambda expressions or smart pointers in this case? 在这种情况下,不正确使用lambda表达式或智能指针是否存在问题? I can't seem to find the explanation in the C++ standard for my situation. 我似乎无法在C ++标准中找到适合我的情况的解释。

Thanks for all the comments explaining what's going on! 感谢您对所有解释一切的评论!

std::make_shared<Class2>(Class2(3, 5)) creates a temporary Class2 , which is moved into shared_ptr storage, and then the original is destroyed. std::make_shared<Class2>(Class2(3, 5))创建一个临时Class2 ,将其移到shared_ptr存储中,然后将其销毁。

Your lambda captures an address of that temporary, and uses it after the temporary is destroyed. 您的lambda会捕获该临时地址,并在销毁该临时地址后使用它。

You need to avoid creating the temporary and construct Class2 directly in shared_ptr storage: std::make_shared<Class2>(3, 5); 您需要避免创建临时类,并直接在shared_ptr存储中构造Class2std::make_shared<Class2>(3, 5); .

Or even better, get rid of the lambda. 甚至更好,摆脱lambda。 I'm not sure why you want to use it there. 我不确定为什么要在那里使用它。

The compiler did what you told it to. 编译器按照您的指示进行了操作。

If so, why didn't the compiler reassign the proper value of this in the storage of m_func? 如果是这样,为什么编译器没有在m_func的存储中重新分配此值?

because you never told it to? 因为你从未告诉过?

[i, this](int x)
{
    int val = *m_data_ptr;
    return (val + m_data + i)*x;
};

the value of this is captured the moment you create the lambda. 创建lambda时即会捕获this值。

No magic, other than dereferencing it when you use a member variable, happens. 除了使用成员变量时取消引用之外,没有任何魔术发生。

Then you copy m_func from one object to another; 然后,将m_func从一个对象复制到另一个; the pointer in the lambda still points at the old object. Lambda中的指针仍然指向旧对象。 Then the old object is destroyed. 然后,旧物体被销毁。 Then you call m_func . 然后,您调用m_func

Your copy constructor is broken. 您的副本构造函数已损坏。 Delete it: 删除它:

  class Class1 {
    Class1(Class1 const&)=delete;

because you store a pointer-to-yourself in the guts of m_func and you never fix it while copying. 因为您将指向自己的指针存储在m_func并且在复制时从不对其进行修复。

This results in your code not compiling; 这导致您的代码无法编译; a fix makes it compile and removes the bug as well: 修复程序使其可以编译并删除该错误:

 class2_ptr = std::make_shared<Class2>(3, 5);

the original created a temporary Class2 , then copied it into the shared ptr; Class2创建了一个临时的Class2 ,然后将其复制到共享的ptr中; this version creates one directly in place. 此版本直接创建一个。

Another approach is to fix the broken m_func : 另一种方法是修复损坏的m_func

std::function<int(Class1*, int)> m_func;

and

m_func =  [i](Class1* self, int x)
{
    int val = *self->m_data_ptr;
    return (val + self->m_data + i)*x;
};

And where you use it: 以及在哪里使用它:

return m_class1obj.m_func(&m_class1obj, v);

and now you no longer have to =delete Class1 's copy ctor, nor change how you construct it. 现在您不再需要=delete Class1的副本ctor,也无需更改其构造方式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM