【问题标题】:c++: Program crashes with parameter (by value) passed to a lambdac ++:程序崩溃,参数(按值)传递给lambda
【发布时间】:2026-01-10 19:35:01
【问题描述】:

我简化了代码,请原谅我的风格。

我想知道当这个 lambda 本身是另一个线程的回调时,由实际分配内存并按值传递给 lambda 的构造函数构造的对象会发生什么情况。 当调用析构函数时看到程序崩溃并不让我感到惊讶。这是测试#1

test#2:我从 A 的 c'tor 和 d'tor 中删除了“new”和“delete[]”,现在 - 它工作正常。

测试#3: 我像以前一样带回了“new”和“delete[]”,但是现在我将每个带有“A objA”(按值)的地方都改成了“A& objA”,现在它也没有崩溃了。

现在,我可以通过挥手使其合理化,但我想了解这里真正发生了什么,就此而言 - 如果通过“捕获”传递给 lambda 的对象也停止存在。

最后一个问题:在这种情况下,有没有好的做法或提示应该做什么(或避免什么)?

#include <iostream>
#include <thread>
#include <future>
#include <chrono>

using namespace std::chrono_literals;

class A {
public:
    A() : x(1) { ptr = new char[1024]; }
    ~A() { delete[](ptr); }
    int getX() { return x; }
private:
    int x = 0;
    char* ptr = nullptr;
};

std::function<void(A objA)> myCb;

int myThread()
{
    static int counter = 0;
    auto a = new A;
    while (true) {
        
        std::this_thread::sleep_for(2s);
        if (myCb)
            myCb(*a);
        else
            std::cout << "myCb is still NULL: counter = " << counter << std::endl;

        if (counter++ == 5)
            break;
    }
    return 0;
}

void registerCallback(std::function<void(A obj)> cb)
{
    myCb = cb;
}

int main()
{
    std::thread t1(myThread);

    std::this_thread::sleep_for(6s);
    int val = 5;
    registerCallback([&val](A objA) {
        std::cout << "here lambda is called with " << objA.getX() << " and " << val << std::endl;
        });

    val = 6;
    std::this_thread::sleep_for(1s);
    val = 7;
    std::this_thread::sleep_for(1s);
    val = 8;
    std::this_thread::sleep_for(1s);
    t1.join();
}

【问题讨论】:

  • 我不认为在没有同步的情况下允许在另一个线程中读取 std::function 时分配给它。

标签: c++ lambda callback pass-by-reference pass-by-value


【解决方案1】:

class A 违反了Rule of 3/5/0,因为它没有实现复制构造函数和/或移动构造函数,或复制赋值和/或移动赋值运算符。

因此,当A 的实例按值 传递时,会生成一个浅拷贝,它共享同一个char* 指向单个@987654325 的指针@array 在内存中,因此当尝试多次 delete[] 同一个数组时,代码可能会崩溃(即,未定义的行为)。

您需要的是深拷贝,以便A 的每个实例分配自己的char[] 数组,例如:

class A
{
public:
    A() : x(1), ptr(new char[1024])
    {
        std::fill(ptr, ptr + 1024, '\0');
    }

    A(const A &src) : x(src.x), ptr(new char[1024])
    {
        std::copy(src.ptr, src.ptr + 1024, ptr);
    }

    A(A &&src)
        : x(src.x), ptr(src.ptr)
    {
        src.ptr = nullptr;
    }

    ~A()
    {
        delete[] ptr;
    }

    A& operator=(A rhs)
    {
        std::swap(x, rhs.x);
        std::swap(ptr, rhs.ptr);
        return *this;
    }

    int getX() const { return x; }

private:
    int x;
    char* ptr;
};

实现这一点的更简单方法是使用std::vector 而不是new[],因为vector 已经符合 3/5/0 规则,因此编译器生成的构造函数、析构函数和赋值运算符对于A 将足以为您复制/移动vector,例如:

#include <vector>

class A
{
public:
    A() : vec(1024, '\0') {}

    int getX() const { return x; }

private:
    int x = 1;
    std::vector<char> vec;
};

【讨论】:

    【解决方案2】:

    您应该使用 unique_ptr。删除 void* 是未定义的行为

    #include <iostream>
    #include <thread>
    #include <future>
    #include <chrono>
    
    using namespace std::chrono_literals;
    
    class A {
    public:
        A() : x(1)
        {       
            ptr = std::make_unique<char[]>(1024);
        }
        ~A()
        {
        }
        int getX() { return x; }
    private:
        int x = 0;
        std::unique_ptr<char[]> ptr = nullptr;
    };
    
    std::function<void(A& objA)> myCb;
    
    int myThread()
    {
        static int counter = 0;
        auto a = new A;
        while (true) {
    
            std::this_thread::sleep_for(2s);
            if (myCb)
                myCb(*a);
            else
                std::cout << "myCb is still NULL: counter = " << counter << std::endl;
    
            if (counter++ == 5)
                break;
        }
        return 0;
    }
    
    void registerCallback(std::function<void(A& obj)> cb)
    {
        myCb = cb;
    }
    
    int mymain()
    {
        std::thread t1(myThread);
    
        std::this_thread::sleep_for(6s);
        int val = 5;
        registerCallback([&val](A& objA) {
            std::cout << "here lambda is called with " << objA.getX() << " and " << val << std::endl;
        });
    
        val = 6;
        std::this_thread::sleep_for(1s);
        val = 7;
        std::this_thread::sleep_for(1s);
        val = 8;
        std::this_thread::sleep_for(1s);
        t1.join();
    
    
        return 0;
    }
    

    【讨论】:

    • 我看不到他们deleteed a void*在提问者的代码中的哪个位置
    • 代码被重写,原来 ptr 被声明为 void*