You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免C++存储的lambda跨线程调用时捕获的this指针失效?

本问题是该问题的延伸,二者场景相似,但原问题未涉及多线程,无法完全解答我的疑惑。

问题背景

如标题所述,我在使用lambda和std::thread时遇到了现有知识无法解决的问题:我尝试将包含成员函数的std::function存储在静态类中,后续在独立的std::thread中调用。问题在于该成员函数需要引用this实例指针以修改所属类的数据成员,但lambda中引用捕获的this指针在成员函数调用时已经失效/销毁,触发了未定义行为。该问题在讨论lambda与并发的提案文档P0018R3中也有相关提及。

最小可复现示例

//Function class
struct FuncWrapper {
    //random data members
    std::string name;
    std::string description;
    //I still have this problem even if this member is const
    std::function<void()> f;
};
class FuncHolder {
public:
    //For the FuncWrapper argument, I've tried pass by reference, const-reference, rvalue, etc. doesn't make a difference in terms of the problem (as I expect)
    //add a FuncWrapper object to the static holder
    static void add(FuncWrapper f) {
        return const_cast<std::vector<FuncWrapper>&>(funcs).push_back(f);
    }
    //Same here with the return
    //get a static FuncWrapper object by index (with bounds checking)
    static const std::function<void()>& get(size_t index) {
        return funcs.at(index).f;
    }
private:
    //C++17 inline static definition; If I weren't using it I would do it the non-inline way
    const static inline std::vector<FuncWrapper> funcs;
};
//Data member class
struct Base {
    //This is meant to be like a constexpr; its unique for each derived type
    inline virtual const char* const getName() const = 0;
protected:
    //This will be important for a solution I tried
    const size_t storedIdx = 0;
};
class Derived : public Base {
public:
    Derived(){
        FuncHolder::add({"add5", "adds 5", 
            [&](){increment(5);} //This is the problem-causing statement
        });
        FuncHolder::add({"add1", "adds 1", 
            std::bind(&Derived::increment, this, 1) //This syntax also causes the same problem
        }); 
    }
    inline const char* const getName() const override {
        return "Derived";
    }
    void increment(int amount){
         //Do some stuff...
         privMember += amount;
         //Do some other stuff..
    }
private:
    int privMember = 0;
};

//Class to hold the static instances of the different derived type
class BaseHolder {
public:
    //make a new object instace in the static vector
    template<class DerivedTy>
    static void init(const DerivedTy& d){
        static_assert(std::is_base_of_v<Base, DerivedTy>); //make sure it's actually derived
        size_t idx = baseVec.size(); //get the index of the object to be added
        const_cast<std::vector<std::unique_ptr<Base>>&>(baseVec).emplace_back(std::make_unique<DerivedTy>(d)); //forward a new unique_ptr object of derived type
        const_cast<size_t&>(baseVec.at(idx)->storedIdx) = idx; //store the object's index in the object itself
    }
    ///This function is used later for one of the solutions I tried; it goes unused for now
    ///So, assume the passed size_t is always correct in this example
    ///There's probably a better way of doing this, but ignore it for the purposes of the example
    //get a casted reference to the correct base pointer
    template<class DerivedTy>
    static DerivedTy& getInstance(size_t derivedIdx){
        return *(static_cast<DerivedTy*>(baseVec.at(derivedIdx).get()));
    }
private:
    //C++17 inline static again
    const static inline std::vector<std::unique_ptr<Base>> baseVec{};
};
int main() {
    BaseHolder::init(Derived()); //add a new object to the static holder
    //Do stuff...
    std::thread runFunc([&](){
        FuncHolder::get(0)(); //Undefined behavior invoked here; *this pointer used in the function being called is already destroyed
    });
    //Main thread stuff...
    runFunc.join();
    return 0;
}

该示例不算极致精简,但我特意标注了函数存储、调用类逻辑等核心细节,方便明确问题来源。
另有几点和设计相关的关键说明如下:

  • 设计预期存在多个继承Base的派生类(如Derived1、Derived2等),但每个派生类仅存在1个实例,因此BaseHolder的所有成员均为静态。若需要调整设计,请优先满足该约束。
  • 可以直接将BaseHolder实现为模板类,编译期传入需存储的派生类类型,使用tuple替代vector,但我刻意未采用该方案,因为后续可能需要在运行时动态添加派生类类型。
  • 我无法修改f的模板类型(即std::function<>的签名),因为需要兼容不同返回值、不同入参的函数,通过lambda或std::bind包装为无返回无入参的std::function<void()>。
  • 设计的核心目标是实现类似事件触发的静态调用逻辑:被调用的函数提前构造完成,调用时可修改其所属类的成员(本示例中为Derived类的成员)。

问题来源

根据我的现有知识,lambda中引用捕获的this指针,在其他线程执行lambda时确实可能失效。调试发现甚至在Derived构造函数中构造lambda时,this指针就已经被销毁,这和我的认知不符,无法100%确认该现象的成因:调试器显示Derived的this实例全部为垃圾值,内存不可读。

Derived(){
    FuncHolder::add({"add5", "adds 5", //`this` pointer is fine here
       [&](){increment(5);} //`this` pointer is filled with junk and pointing to a different random address
    });
    //...
}

我可以确认lambda/函数调用时触发了未定义行为,是由于Derived的this实例指针已销毁:每次运行抛出的异常类型、来源文件均不相同,有时直接触发读取访问冲突,调试器也无法读取lambda中this指针的内存,均符合指针销毁的特征。
我之前处理过无线程场景下的同类lambda问题,但多线程场景让问题变得更复杂,后续会展开说明。

已尝试的解决方案

值捕获

最简单的方案是使用C++17特性,让lambda值捕获Derived的this指针(前述问题的回答和P0018R3提案均提及该方案),提案中也明确提到线程等并发场景下需要值捕获this:

Derived(){
    FuncHolder::add({"add5", "adds 5", 
        [&, *this](){increment(5);} //Capture *this by value (C++17); it's thread-safe now
    });
    //...
}

但该方案的问题在于,lambda中调用的函数需要修改原类的成员,值捕获this只会修改Derived实例的副本,无法修改目标原实例。

使用静态实例

既然每个派生类只有一个静态实例,且存在静态的派生类持有者,那么直接在lambda中使用静态实例,修改该实例即可:

Derived(){
    FuncHolder::add({"add5", "adds 5", 
        [=](){BaseHolder::getInstance(storedIdx).increment(5);} //use static instance in lambda; Again assume the passed index is always correct for this example
    });
    //...
}

该方案逻辑上可行,但存在问题:getInstance()在构造函数中调用,此时实例还未完成构造。具体来说,Derived()构造函数在BaseHolder::init(Derived())中被调用,init才会将实例存入vector,而Derived()构造函数执行时init还未完成vector写入操作,访问vector会出错。

向成员函数传入静态实例

前述问题的另一个回答提到,可以修改lambda中调用的函数,新增所属类实例的入参,本示例中实现如下:

class Derived : public Base {
public:
    Derived(){
        FuncHolder::add({"add5", "adds 5", 
            [&](){increment(BaseHolder::getInstance(storedIdx), 5);} //Pass the static instance to the actual function
        });
        //...
    }
    //rest of the class...
    void increment(Derived& instance, int amount){
         //Do some stuff...
         instance.privMember += amount;
         //Do some other stuff..
    }
private:
    int privMember = 0;
};

但该方案和上一方案存在相同问题:构造函数执行时静态实例还未创建,访问实例会出错。

直接使用this的shared_ptr

前述问题多次提到可以使用shared_ptr(或任意智能指针)持有this,延长其生命周期(但回答未给出具体实现)。直接实现的方式如下:

Derived(){
    FuncHolder::add({"add5", "adds 5", 
        [self=std::shared_ptr<Derived>()](){self->increment(5);} //pass a shared_ptr of *this; syntax can differ
    });
    //...
}

该方案会抛出std::bad_weak_ptr异常,实现方式本身存在问题。

通过std::enable_shared_from_this<T>获取this的shared_ptr

我在无线程场景下常用的方案,以及该博客提到的方案,是继承std::enable_shared_from_this<T>,调用shared_from_this()获取合法的this的shared_ptr:

class Derived : public Base, public std::enable_shared_from_this<Derived> {
    Derived(){
        FuncHolder::add({"add5", "adds 5", 
            [self=shared_from_this()](){self->increment(5);} //pass a shared_ptr of *this
        });
        //...
    }
    //rest of class...
};

该方案逻辑上可行,也不会抛出异常,但实际运行问题依旧存在,和直接引用捕获this的表现没有区别。

最终问题

是否可以避免lambda中派生类的this指针在跨线程调用时失效?如果不行,如何调整设计可以实现目标需求,同时保留现有的设计约束?


内容的提问来源于stack exchange,提问作者Abob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 04:36:04