Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++ threads, should I pass shared_ptr by value or reference?

Tags:

c++

This page on Thread Safety by Microsoft says shared_ptr should be used even if there are multiple copies sharing the same object.

So does this mean that both of the following are acceptable? I've tried both and they appear to work fine.

EDIT: The actual business objective is to get string updates from the long running thread to the main thread. I figured I should use shared_ptr since string is not thread safe. Don't care about ownership honestly.

Option 1 (Passing reference):

auto status = std::make_shared<std::string>();
auto f = [&status]() {
    ...
  *status = "current status";
    ...
};

std::thread t{f};

while(true) {
  std::cout << *status << std::endl;
  std::this_thread::sleep_for(1000ms);
  if (*status == "completed") break;
}

t.join();

Option 2 (Making a copy):

auto status = std::make_shared<std::string>();
auto f = [](std::shared_ptr<std::string> s) {
    ...
  *s= "current status";
    ...
};

std::thread t{f, status};

while(true) {
  std::cout << *status << std::endl;
  std::this_thread::sleep_for(1000ms);
  if (*status == "completed") break;
}

t.join();

EDIT2: So apparently both these approaches are wrong for what I'm trying to achieve. I need to use std::mutex (cppreference) and not muck around with shared_ptr. See second half of this answer.

like image 687
Plasty Grove Avatar asked Mar 03 '21 10:03

Plasty Grove


People also ask

Is shared_ptr reference counting thread safe?

A std::shared_ptr consists of a control block and its resource. Yes, the control block is thread-safe; but no, the access to the resource is not thread-safe. That means, modifying the reference counter is an atomic operation and you have the guarantee that the resource will be deleted exactly once.

Is shared_ptr reference counting?

The shared reference counter counts the number of owners. Copying a std::shared_ptr increases the reference count by one. Destroying a std::shared_ptr decreases the reference count by one. If the reference count becomes zero, the resource will automatically be released.

Should you pass shared pointers by reference?

In controlled circumstances you can pass the shared pointer by constant reference. Be sure that nobody is concurrently deleting the object, though this shouldn't be too hard if you're careful about to whom you give references. In general, you should pass the shared pointer as a straight copy.

When should you use shared_ptr?

So, we should use shared_ptr when we want to assign one raw pointer to multiple owners. // referring to the same managed object. When to use shared_ptr? Use shared_ptr if you want to share ownership of a resource.

What is shared_ptr in C++?

(since C++11) std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of the following happens:

Is it possible to pass a shared pointer by reference?

In controlled circumstances you can pass the shared pointer by constant reference. Be sure that nobody is concurrently deleting the object, though this shouldn't be too hard if you're careful about to whom you give references. In general, you should pass the shared pointer as a straight copy.

Should we pass shared_ptr by value or by ref In lambdas?

The habit of passing shared_ptr by ref should not be followed in lambdas. If it gets destroyed elsewhere (passing by ref doesn't bump the ref count), your callback/lambda may crash. OTOH, passing it by value in lambdas too is dangerous and can cause memory leaks. Instead, we should pass weak_ptrto a shared_ptr.

How do you pass a shared_ptr to a function?

Then it needs its own copy of the shared_ptr. So pass it by value. If a function simply needs to access an object owned by the caller, go ahead and pass by (const) reference, to avoid the overhead of copying the shared_ptr. The best practice in C++ is always to have clearly defined ownership semantics for your objects.


2 Answers

Typically, threads may outlive the scope where they are created. In such case, any local variable captured by reference may be destroyed while the thread is still running. If this is the case, then you should not capture by reference.

Furthermore, modifying a shared pointer object in one thread and accessing in another without synchronisation results in undefined behaviour. If that is what you're doing, then you should access the pointer using std::atomic_load/atomic_store functions, or simply copy the pointer into each thread. Note that you can capture by copy:

auto f = [status]() {

Furthermore, the shared pointer provides no extra thread safety to accessing the pointed object beyond keeping the ownership alive and ensuring it gets deleted exactly once. If the pointed type is not atomic, then modifying it in one thread and accessing in another without synchronisation results in undefined behaviour. If that is what you're doing, you need to use mutexes or something similar. Or copy the pointed object itself into each thread.

Regarding the edited question: Your examples apply to this last case. Both of them have undefined behaviour. You need synchronisation.

like image 168
eerorika Avatar answered Jan 04 '23 19:01

eerorika


It is weird to accept shared_ptr by reference as you lose the whole point of using shared_ptr in the first place. You may just use a raw pointer instead.

There are cases when accepting by reference of shared_ptr is legitimate but if you give a reference of it to a thread then it will cause UB once that instance of the shared_ptr is destroyed and the thread still uses the shared_ptr.

Primary purpose of shared_ptr is to manage lifetime of the object. If you pass a reference of it to a thread then you throw away the whole purpose and advantages of the shared_ptr.

like image 29
ALX23z Avatar answered Jan 04 '23 19:01

ALX23z