这里的派生类指针可能存在生命周期问题吗?

问题描述

#include <memory>
#include <iostream>
#include <thread>

class IA
{
    public:
        virtual ~IA() = default;
        virtual void foo(void) = 0;
};

class A : public IA
{
    private:
        int x;
    
    public:
        A(int _x) : x(_x) {}
        void foo(void) { std::cout << "Hello\n"; }
};

int main(void)
{
    std::unique_ptr<IA> ia_ptr = std::make_unique<A>(10);
    auto t1 = std::thread(&A::foo,dynamic_cast<A*>(ia_ptr.get()));
    
    t1.join();
    return 0;
}

这是我正在处理的一段代码的简化视图。 我需要使用线程来调用函数foo(在派生类A::foo中的实现)。

由于派生类的对象只能通过unique_ptr访问(设计约束)基类,因此我将线程对象称为:

std::thread t1 = std::thread(&A::foo,dynamic_cast<A*>(ia_ptr.get()));

我的第一个问题是,由于A对象的生存期与程序的生存期一样长(在这种情况下,unique_ptr仅在程序退出时才被销毁),因此是否存在生命周期/悬挂指针与上面显示的方式调用线程对象相关的类型风险?

我的第二个问题是,由于foo()仅在A::foo中实现,std::thread(&A::foo,dynamic_cast<A*>(ia_ptr.get()));std::thread(&IA::foo,ia_ptr.get());之间在概念上有什么区别吗?我的看法是,在两种情况下,都使用foo()的相同实例调用A的相同实现。

这种解释正确吗?

仅供参考,代码使用两种方法进行编译。

解决方法

首先,A::foo()应该标记为override

class A : public IA
{
    ...
    
    public:
        ...
        void foo(void) override { ... }
};

也就是说,在这种情况下,使用从类型转换的std::threadA::foo()指针直接使IA*调用A*会达到使用{ {1}}。您也可以完全摆脱IA并单独使用IA

A

否则,如果您想使用auto a_ptr = std::make_unique<A>(10); auto t1 = std::thread(&A::foo,a_ptr.get()); ,则应该实际使用它。相反,请使用原始的IA指针进行std::thread调用IA::foo(),然后让多态正常地将调用分派到IA*

A::foo()

或者更好的是,甚至根本没有直接std::unique_ptr<IA> ia_ptr = std::make_unique<A>(10); auto t1 = std::thread(&IA::foo,ia_ptr.get()); 调用std::thread。改用lambda,然后让编译器计算出正确的调度,以便为您调用foo()

foo()

不,这里没有悬空指针问题,因为正如您所说的,std::unique_ptr<IA> ia_ptr = std::make_unique<A>(10); auto t1 = std::thread([&](){ ia_ptr->foo(); }); 在使用完线程后被销毁了。