What's the most effecient way to pass a copy of std::shared_ptr into a C function/API/thread that takes only a const void* parameter?

huangapple go评论75阅读模式
英文:

What's the most effecient way to pass a copy of std::shared_ptr into a C function/API/thread that takes only a const void* parameter?

问题

如果我有一个在不同线程中调用回调的 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;
}
英文:

Say, if I have a C function (or an API) that invokes a callback in a different thread and accepts an old-school C parameter in a form of const void*. What's the most efficient way to pass a copy of the std::shared_ptr into that callback?

So far, I've been using the following method:

(I'll use a thread to simulate what I'm asking.)

struct My1
{
    My1()
    {
        std::cout &lt;&lt; &quot;My1 constructor&quot; &lt;&lt; std::endl;
    }
    
    ~My1()
    {
        std::cout &lt;&lt; &quot;My1 destructor&quot; &lt;&lt; std::endl;
    }
    
};

void* thread1(void* pParam)
{
    std::shared_ptr&lt;My1&gt;* pp1 = (std::shared_ptr&lt;My1&gt;*)pParam;
    
    std::cout &lt;&lt; &quot;thread1 ref count: &quot; &lt;&lt; pp1-&gt;use_count() &lt;&lt; std::endl;
    
    delete pp1;

    return 0;
}

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

    return 0;
}

答案1

得分: 1

由于您只需要代码部分的翻译,这里是代码的翻译:

自您需要一份拷贝,只需在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) {
    // 拷贝构造:
    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) {
        // 线程启动失败
    } else {
        pthread_join(thr1, nullptr); // 等待线程结束
    }
    std::cout << "ref count: " << p1.use_count() << '\n';
}

输出:

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

Since you want a copy, just create an instance in thread1 using the copy constructor. Also, don't create the shared_ptrs using new. It doesn't make much sense.

Example:

#include &lt;pthread.h&gt;

#include &lt;iostream&gt;
#include &lt;memory&gt;

struct My1 {
    My1() { std::cout &lt;&lt; &quot;My1 constructor\n&quot;; }
    ~My1() { std::cout &lt;&lt; &quot;My1 destructor\n&quot;; }
};

void* thread1(void* pParam) {
    // copy construct:
    std::shared_ptr&lt;My1&gt; pp1 = *static_cast&lt;std::shared_ptr&lt;My1&gt;*&gt;(pParam);
    std::cout &lt;&lt; &quot;thread1 ref count: &quot; &lt;&lt; pp1.use_count() &lt;&lt; &#39;\n&#39;;
    return nullptr;
}

int main() {
    std::shared_ptr&lt;My1&gt; p1 = std::make_shared&lt;My1&gt;();
    std::cout &lt;&lt; &quot;ref count: &quot; &lt;&lt; p1.use_count() &lt;&lt; &#39;\n&#39;;

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

Output:

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

答案2

得分: 0

I am going to assume that the lifetime of the callback thread may exceed the lifetime of the original shared_ptr used when calling into the API. If this is not the case and the original shared_ptr is guaranteed to live longer than the callback thread, you can dispense with the smart pointer and just pass in a raw pointer and not worry. I presume this is not the case.

This is not aesthetically pleasing code, but I think it is a reasonable (only?) option for the situation you describe of calling into an API that invokes a callback you provide.

英文:

I am going to assume that the lifetime of the callback thread may exceed the lifetime of the original shared_ptr used when calling into the API. If this is not the case and the original shared_ptr is guaranteed to live longer than the callback thread, you can dispense with the smart pointer and just pass in a raw pointer and not worry. I presume this is not the case.

This is not aesthetically pleasing code, but I think it is a reasonable (only?) option for the situation you describe of calling into an API that invokes a callback you provide.

答案3

得分: 0

不要动态分配共享指针;在此处使用自动存储期;否则使用智能指针的整个意义基本上丧失。

此外,由于您在主线程上执行 delete pp1;,不能保证在后台线程访问时共享指针对象仍然存在。

在这里,您需要一种方式来传达后台线程已经拥有了对象的事实,并以一种方式来建立上下文对象与后台线程拥有共享指针之间的发生前关系。

std::latch

英文:

Do not dynamically allocate shared pointers; work with automatic storage duration here; otherwise the whole point of using smart pointers is basically lost.

Furthermore since you do delete pp1; on the main thread, it's in no way guaranteed that the shared pointer object is still alive when you access it in the background thread.

What you need here is a way of communicating the fact that the background thread has taken ownership of the object and doing so in a way that establishes a happens before relationship between the context object and the background thread taking ownership of the shared pointer.

std::latch happens to be something that can accompilish this. You can work around the const-ness of the context by using mutable (. Note: pthread does not receive the context as pointer to const).

Note: I'm using std::thread here for simplicity, but since it's used with a function pointer and a void pointer, you should easily be able to adjust the logic for use with pthread or other C multithreading logic.

struct ThreadContext
{
    mutable std::latch m_latch{1};
    mutable std::shared_ptr&lt;std::string&gt; m_ptr = std::make_shared&lt;std::string&gt;(&quot;Hello World&quot;);
};

void BackgroundThreadLogic(void const* context)
{
    std::shared_ptr&lt;std::string&gt; ptr;

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

    std::cout &lt;&lt; &quot;Message received on background thread: &quot; &lt;&lt; *ptr &lt;&lt; &#39;\n&#39;;
}

int main() {
    std::thread t;

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

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

huangapple
  • 本文由 发表于 2023年5月22日 02:00:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76301239.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定