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

时间:2019-05-20 22:44:06

标签: c++ lambda shared-ptr

我在编写一个包含lambda表达式和共享工作指针的普通C ++项目时遇到了问题。该项目在Visual Studio,Debug,x64中。这是我的代码。

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;
}

代码可以正常编译,但是在执行main()的最后一行时会崩溃。调试时,我发现问题在第class2_ptr = std::make_shared<Class2>(Class2(3, 5));行结束时就发现了,由于某种原因,当第m_func = ...行中的lambda在this对象的构造时间捕获了Class1指针时, ,它会在创建Class2的智能指针后立即记录与对象地址不同的地址!似乎第一个记录的地址已过时。当我调用class2_ptr->Compute(23)时,我最终在int val = *m_data_ptr;处取消了空指针的引用,从而导致崩溃,即使在调用{{1}之前在class2_ptr->Assign(val);中分配了非空地址也是如此}!但是,m_func的地址为什么要更改?是因为对象的内部重新分配对用户隐藏了吗?如果是这样,为什么编译器没有在this的存储中重新分配this的正确值?此外,尽管在m_func中记录了Class1对象的错误地址,但在m_func中用正确的值访问了另一个数据成员m_data

重要的一点是,如果我注释掉main()的最后四行并删除其他三行的注释,则程序不会崩溃!显然,使用共享指针与它有关。 在这种情况下,不正确使用lambda表达式或智能指针是否存在问题?对于我的情况,我似乎找不到C ++标准中的解释。

感谢所有评论,说明发生了什么事!

2 个答案:

答案 0 :(得分:5)

std::make_shared<Class2>(Class2(3, 5))创建一个临时Class2,将其移至shared_ptr存储中,然后销毁原始文件。

您的lambda会捕获该临时地址,并在该临时地址被销毁后使用。

您需要避免创建临时文件,并直接在Class2存储shared_ptr中构造std::make_shared<Class2>(3, 5);

甚至更好,摆脱lambda。我不确定为什么要在那里使用它。

答案 1 :(得分:2)

编译器按照您的指示进行操作。

  

如果是这样,为什么编译器没有在m_func的存储中重新分配此值?

因为你从未告诉过它?

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

创建lambda时将捕获this的值。

除了使用成员变量时取消引用,没有其他魔术。

然后将m_func从一个对象复制到另一个; Lambda中的指针仍然指向旧对象。然后,旧物体被销毁。然后,您致电m_func

您的副本构造函数已损坏。删除它:

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

因为您将自己的指针存储在m_func的内部,并且在复制时从不修复它。

这将导致您的代码无法编译;修复程序使其可以编译并同时消除了该错误:

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

原始文件创建了一个临时Class2,然后将其复制到共享的ptr中;此版本直接在原位创建一个。

另一种方法是修复损坏的m_func

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

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

以及使用位置:

return m_class1obj.m_func(&m_class1obj, v);

现在您不再需要=delete Class1的副本ctor了,也无需更改其构造方式。