问题描述
#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::thread
到A::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(); });
在使用完线程后被销毁了。