在使用共享指针调用的函数中使用它是有效的

时间:2018-06-08 13:24:01

标签: c++ shared-ptr

我有一个关于在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;
}

谢谢 乔治

2 个答案:

答案 0 :(得分:2)

如果在堆上分配对象(使用new),则在线程完成时它不会变为无效。 shared_pointer存储指向堆上实例的指针和一个指向count变量的指针。如果复制shared_pointer,则count变量会增加,如果调用析构函数,则会减少。如果计数为0,则由共享指针调用对象的析构函数,因此如果没有shared_pointer s指向该对象。 shared_pointer应该是线程保存,即使这是非常强大的资源。重要的是要知道,如果您将指针传递给shared_pointershared_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 *?

是有效的

您的foofoo课程并非来自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::barB* p = new B(); p->bar(); this版本的void *传递给this等其他函数无效当A::foo取消引用该指针时,不确保指针指向的实例仍然有效。

如果您使用A::foo来控制对象的生命周期,那么理想情况下您希望传递shared_ptr<T> A::foo个参数。这样,即使所有其他共享指针都被销毁,对象也会被shared_ptr<B>传递给shared_ptr