c++ 将std::shared_ptr的副本传递给只接受const void* 参数的C函数/API/线程的最有效方法是什么?

p5fdfcr1  于 2023-05-24  发布在  其他
关注(0)|答案(3)|浏览(170)

比如说,如果我有一个C函数(或API),它在不同的线程中调用回调,并接受const void*形式的老式C参数。将std::shared_ptr的副本传递到回调函数中的最有效方法是什么?
到目前为止,我一直在使用以下方法:
(我将使用一个线程来模拟我所要求的内容。)

struct My1
{
    My1()
    {
        std::cout << "My1 constructor" << std::endl;
    }
    
    ~My1()
    {
        std::cout << "My1 destructor" << std::endl;
    }
    
};

void* thread1(void* pParam)
{
    std::shared_ptr<My1>* pp1 = (std::shared_ptr<My1>*)pParam;
    
    std::cout << "thread1 ref count: " << pp1->use_count() << std::endl;
    
    delete pp1;

    return 0;
}

int main(int argc, const char * argv[])
{
    if(1)
    {
        std::shared_ptr<My1> p1 = std::make_shared<My1>();
        
        std::cout << "ref count: " << p1.use_count() << std::endl;
        
        std::shared_ptr<My1>* pp1 = new std::shared_ptr<My1>(p1);
        pthread_t thr1;
        if(pthread_create(&thr1, nullptr, thread1, (void*)pp1) != 0)
        {
            //Failed to start thread
            delete pp1;
        }
        
        std::cout << "ref count: " << p1.use_count() << std::endl;
    }
    
    int i;
    std::cin >> i;

    return 0;
}
62lalag4

62lalag41#

我假设回调线程的生命周期可能超过调用API时使用的原始shared_ptr的生命周期。如果不是这种情况,并且原始shared_ptr保证比回调线程更长,则可以省去智能指针,只需传入一个原始指针,而不必担心。我想情况并非如此。
这是不美观的代码,但我认为这是一个合理的(只?)选项,用于您所描述的调用您提供的回调的API的情况。

8ehkhllq

8ehkhllq2#

不动态分配共享指针;在此自动存储持续时间;否则使用智能指针的全部意义基本上就丧失了。
此外,由于您在主线程上执行delete pp1;,因此无法保证当您在后台线程中访问共享指针对象时,它仍然是活动的。
这里需要的是一种方法来传达这样一个事实,即后台线程已经获得了对象的所有权,并且这样做的方式是在上下文对象和获得共享指针所有权的后台线程之间建立一种happens before关系。
std::latch恰好是可以实现这个的东西。您可以通过使用mutable(.注意:pthread接收context作为const的指针)。

**注意:**为了简单起见,我在这里使用std::thread,但由于它与函数指针和void指针一起使用,您应该可以轻松地调整逻辑以用于pthread或其他C多线程逻辑。

struct ThreadContext
{
    mutable std::latch m_latch{1};
    mutable std::shared_ptr<std::string> m_ptr = std::make_shared<std::string>("Hello World");
};

void BackgroundThreadLogic(void const* context)
{
    std::shared_ptr<std::string> ptr;

    {
        auto const tk = static_cast<ThreadContext const*>(context);
        ptr = std::move(tk->m_ptr);
        tk->m_latch.count_down(); // after this the context may become a dangling pointer any time, but we've taken ownership of the important stuff already
    }

    std::cout << "Message received on background thread: " << *ptr << '\n';
}

int main() {
    std::thread t;

    {
        ThreadContext context;
        t = std::thread(&BackgroundThreadLogic, static_cast<void const*>(&context));
        context.m_latch.wait();
        // now we can delete the context
    }

    t.join(); // still need to join the thread in the end
}
gijlo24d

gijlo24d3#

因为您需要一个副本,所以只需使用复制构造函数在thread1中创建一个示例。另外,不要使用new创建shared_ptr。这说不通啊
示例:

#include <pthread.h>

#include <iostream>
#include <memory>

struct My1 {
    My1() { std::cout << "My1 constructor\n"; }
    ~My1() { std::cout << "My1 destructor\n"; }
};

void* thread1(void* pParam) {
    // copy construct:
    std::shared_ptr<My1> pp1 = *static_cast<std::shared_ptr<My1>*>(pParam);
    std::cout << "thread1 ref count: " << pp1.use_count() << '\n';
    return nullptr;
}

int main() {
    std::shared_ptr<My1> p1 = std::make_shared<My1>();
    std::cout << "ref count: " << p1.use_count() << '\n';

    pthread_t thr1;
    if (pthread_create(&thr1, nullptr, thread1, &p1) != 0) {
        // Failed to start thread
    } else {
        pthread_join(thr1, nullptr); // wait for the thread to finish
    }
    std::cout << "ref count: " << p1.use_count() << '\n';
}

输出:

My1 constructor
ref count: 1
thread1 ref count: 2
ref count: 1
My1 destructor

相关问题