我有一个关于在C ++中使用共享指针和多线程编程的问题。 如果我在线程A中使用此类的共享指针对象调用成员函数, 然后在类中使用this属性并在将从线程B触发的回调函数中传递它是有效的。 从我的角度来看,如果线程A完成它的工作,那么共享指针将过期,这将是无效的。
使用shared_from_this并将其转换为void *?
是有效的你好,这是代码。这段代码会产生分段错误,对吗?
#include <memory>
#include <iostream>
#include <thread>
#include <unistd.h>
#include "ClassB.h"
class B;
class A
{
public:
A()= default;
void foo(void * ptr)
{
std::cout <<"enter in foo" <<std::endl;
sleep(1);
std::cout << "Thread wake up" << std::endl;
B *pB = reinterpret_cast<B*>(ptr);
pB->x = 5;
}
};
void B::bar()
{
std::cout << "enter in bar" << std::endl;
void * ptr = reinterpret_cast<void *>(this);
auto t2= std::thread(&A::foo,A(),ptr);
t2.detach();
}
void threadTask()
{
std::cout << "ThreadTask" << std::endl;
std::shared_ptr<B> psharedB = std::make_shared<B>();
auto t1 = std::thread(&B::bar,psharedB);
t1.detach();
}
int main()
{
auto t = std::thread(threadTask);
t.join();
sleep(20);
return 0;
}
谢谢 乔治
答案 0 :(得分:2)
如果在堆上分配对象(使用new
),则在线程完成时它不会变为无效。 shared_pointer
存储指向堆上实例的指针和一个指向count变量的指针。如果复制shared_pointer
,则count变量会增加,如果调用析构函数,则会减少。如果计数为0,则由共享指针调用对象的析构函数,因此如果没有shared_pointer
s指向该对象。 shared_pointer
应该是线程保存,即使这是非常强大的资源。重要的是要知道,如果您将指针传递给shared_pointer
,shared_pointer
现在负责管理实例,但它并不关于指针的任何其他用法。因此,您不应该在对象的其他指针上调用delete。
因此,如果使用new
分配对象,则不应导致任何冲突。
编辑:
这应该不起作用,因为线程被分离并且范围保留并且shared_pointer
count=1
应该被销毁,B
将被销毁。
分离线程总是一个坏主意。
答案 1 :(得分:1)
std::shared_ptr<B> psharedB = std::make_shared<B>(); auto t1 = std::thread(&B::bar,psharedB);
我不是语言律师,但我认为上面的代码会创建共享指针B
指向的类psharedB
的实例,然后创建std::thread
的实例将按值传递给方法B::bar
的函数指针作为第一个参数,并传递按值 {{1>}的副本 }作为第二个参数。
因为psharedB
的副本传递给psharedB
构造函数,共享指针的引用计数增加到2。您可以通过将以下代码添加到thread ( … )
来检查此问题,以便在构建新线程后检查threadTask
的值...
psharedB.use_count()
...文档警告use_count返回的值在多线程环境中是“近似的”,但在这种特殊情况下它应该是准确的。
当auto t1 = std::thread(&B::bar, psharedB);
// show use count is two not one
std::cout << "psharedB use count: " << psharedB.use_count() << std::endl;
退出时,threadtask
超出范围。共享指针的使用计数由共享指针的析构函数递减,但psharedB
对象实例保持活动状态,因为传递给B
线程的psharedB
副本仍然存在,因此使用计数为一个不是零。
在t1
中,变量B::bar
是普通的普通原始this
指针。在封面下面,它是从传递给B *
线程的智能指针中提取的,但就t1
中的逻辑而言,我们不知道这一点。因此,当该指针转换为B::bar
时,它根本不会影响共享指针的使用计数。
探索此问题的一种方法是在void *
的{{1}}上放置断点并运行到该点。紧接在断点下方的调用堆栈中的条目将是使用reinterpret_cast
副本调用B::bar
的逻辑。调试器应该允许您查看共享指针并检查其使用计数,如果B::bar
已经退出,则为一个,如果psharedB
尚未退出,则为两个。
跳过threadTask
并观察共享指针的使用计数不会改变。
当threadTask
返回时,reinterpret_cast
启动的B::bar
线程终止。实际上,传递给该线程的t1
副本超出了范围并被破坏。共享指针使用计数变为零,最初由threadTask
创建的psharedB
对象实例也被销毁。
此时传递给B
的{{1}}值是指向已删除对象实例的指针。任何尝试取消引用threadTask
ptr
内的foo
值都会导致未定义的行为。如果你很幸运,它会破坏你的程序。如果你运气不好,那么解除引用似乎有效,并且在程序执行后期你很难找到失败的故障。或者一些奇怪的行为,有时只会发生,很难再现。
更糟糕的是,取决于 时ptr
线程被销毁,因此 时foo
对象实例被销毁,变量{{在t1
中{1}}可能是B
某个ptr
执行的有效foo
指针,以及B*
执行其余部分的无效指针。
使用shared_from_this并将其转换为void *?
是有效的
您的foo
和foo
课程并非来自A
,因此在您的示例中,在任一课程中调用B
无效。
如果 创建了std::enable_shared_from_this<T>
个实例,您会发现无法将其强制转换为std::shared_from_this
。尝试将shared_ptr<B>
添加到void *
。您会发现编译器出错,因为转换是无效转换。
在使用共享指针
调用的函数中使用它是否有效
是的,它是有效的 - 在某些限制内。从auto temp = reinterpret_cast<void *> (psharedB);
调用的方法threadTask
是使用共享指针调用的函数。您可以在B::bar
中使用threadTask
。但是你没有获得this
的任何特殊功能,因为它是使用共享指针调用的。就B::bar
而言,它可以使用像这样的原始指针调用...
this
无论您使用的是共享指针还是原始指针 - 将B::bar
或B* p = new B();
p->bar();
this
版本的void *
传递给this
等其他函数无效当A::foo
取消引用该指针时,不确保指针指向的实例仍然有效。
如果您使用A::foo
来控制对象的生命周期,那么理想情况下您希望传递shared_ptr<T>
A::foo
个参数。这样,即使所有其他共享指针都被销毁,对象也会被shared_ptr<B>
传递给shared_ptr
。