简体   繁体   中英

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. The project is in Visual Studio, Debug, x64. This is the code I have.

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:

#pragma once
#include "Class1.h"

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

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() . While debugging, I found that the problem discovers itself upon completion of the line 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! 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; , thus causing the crash, even though I assigned a non-null address in class2_ptr->Assign(val); prior to calling m_func ! But why did the address of this change? 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 ? 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 .

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! 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? I can't seem to find the explanation in the C++ standard for my situation.

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.

Your lambda captures an address of that temporary, and uses it after the temporary is destroyed.

You need to avoid creating the temporary and construct Class2 directly in shared_ptr storage: std::make_shared<Class2>(3, 5); .

Or even better, get rid of the 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?

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.

No magic, other than dereferencing it when you use a member variable, happens.

Then you copy m_func from one object to another; the pointer in the lambda still points at the old object. Then the old object is destroyed. Then you call 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.

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; this version creates one directly in place.

Another approach is to fix the broken 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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