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

Is valid to use this in a function that is called using shared pointer

我对 C++ 中共享指针的使用和多线程编程有疑问。 如果我在线程 A 中使用 this class 的共享指针对象调用成员函数, 然后在 class 中使用 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;
}

谢谢 乔治

如果对象是在堆上分配的(使用new),它不会在线程结束时变为无效。 shared_pointer 存储了一个指向堆上实例的指针和一个指向 count 变量的指针。如果 shared_pointer 被复制,计数变量会增加,如果它的析构函数被调用,它会减少。如果计数为 0,则对象的析构函数将由共享指针调用,因此如果没有 shared_pointers 指向该对象。 shared_pointer 应该是线程保存,即使这是非常耗费资源的。重要的是要知道,如果您将指针传递给 shared_pointer,则 shared_pointer 现在负责管理该实例,但它不会影响该指针的任何其他用途。因此,您不应在指向该对象的其他指针上调用 delete。 因此,如果您使用 new.

分配对象,则不会引起任何冲突

编辑:

这不应该工作,因为 shared_pointer 将随着线程分离和范围离开而被销毁,并且 count=1 B 应该被销毁。 分离线程总是一个坏主意。

std::shared_ptr<B> psharedB = std::make_shared<B>();
auto t1 = std::thread(&B::bar,psharedB);

我不是语言律师,但我认为上面的代码将创建共享指针 psharedB 指向的 class B 的实例,然后创建一个实例std::thread 按值 传递 一个指向方法B::bar 的函数指针作为第一个参数,并按值 传递一个copy of psharedB 作为第二个参数。

因为 psharedB 的副本被传递给 thread ( … ) 构造函数,共享指针的引用计数增加到两个。您可以通过将以下代码添加到 threadTask 来探索这一点,以在构建新线程后检查 psharedB.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;

…文档警告说 use_count 返回的值在多线程环境中是 "approximate" 但在这种特殊情况下它应该是准确的。

threadtask 退出时,psharedB 超出范围。共享指针的使用计数被共享指针的析构函数减少,但是 B 对象实例保持活动状态,因为传递给 t1 线程的 psharedB 副本仍然存在,所以使用计数是一个不为零。

B::bar 中,变量 this 是一个普通的原始 B * 指针。在幕后,它是从传递给 t1 线程的智能指针中提取的,但就 B::bar 中的逻辑而言,我们并不知道。因此,当该指针被强制转换为 void * 时,它根本不会影响共享指针的使用计数。

探索这一点的一种方法是在 B::bar 和 运行 中的 reinterpret_cast 上放置一个断点。紧接在断点下方的调用堆栈中的条目将是使用 psharedB 的副本调用 B::bar 的逻辑。调试器应该允许您查看共享指针并检查其使用计数,如果 threadTask 已经退出则为 1,如果 threadTask 尚未退出则为 2。

跨过 reinterpret_cast 并观察共享指针的使用计数没有改变。

B::bar returns 时,threadTask 启动的 t1 线程终止。实际上,传递给该线程的 psharedB 副本超出范围并被破坏。共享指针使用计数变为零,最初由 threadTask 创建的 B 对象实例也被销毁。

此时传递给fooptr值是指向已删除对象实例的指针。任何在 foo 中取消引用 ptr 值的尝试都将导致未定义的行为。如果你幸运的话,它会使你的程序崩溃。如果你不走运,取消引用似乎起作用了,你会在程序执行的后期难以追踪失败。或者一些奇怪的行为,只是偶尔发生并且很难重现。

更糟糕的是,根据 whent1 线程被销毁,因此 when B 对象实例被销毁,foo 中的变量 ptr 对于 foo 的某些执行可能是有效的 B* 指针,对于 [=47] 的其余部分可能是无效指针=]的执行。

Is valid to use the shared_from_this and cast this to void*?

您的 AB class 不是从 std::enable_shared_from_this<T> 派生的,因此在您的示例中,在任一 class。

如果您确实 创建了一个shared_ptr<B> 实例,您会发现无法将其转换为void *。尝试将 auto temp = reinterpret_cast<void *> (psharedB); 添加到 threadTask。您会发现编译器错误,因为转换是无效转换。

Is it valid to use this in a function that is called using shared pointer

是的,它是有效的 - 在一定的限制范围内。从 threadTask 调用的方法 B::bar 是一个使用共享指针调用的函数。您可以在 B::bar 中使用 this。但是您不会因为 this 是使用共享指针调用它而获得任何特殊功能。就 B::bar 而言,它可以使用这样的原始指针调用......

B* p = new B();
p->bar();

无论您使用的是共享指针还是原始指针 - 将 thisthisvoid * 版本传递给另一个函数(例如 [=74)是无效的=] 没有确保指针指向的实例在 A::foo 取消引用该指针时仍然有效。

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