2

From Effective Modern C++, Item 21, I learned that one advantage of std::make_shared over new+std::shared_ptr is that code like this

processWidget(std::shared_ptr<Widget>(new Widget), computePriority());

can result in a leaked Widget if computePriority() throws in between new Widget evaluation and the call to std::shared_ptr constructor, wheras in this alternative code that is not possible:

processWidget(std::make_shared<Widget>(), computePriority());

But std::make_shared is itself implemented in terms of new and std::shared_ptr.

So a friend asked me, can anything else, in multithreaded fallacious code, happen in the middle of std::make_shared's execution causing the same effect?

I know little or zero of multithreading, so my question may actually be dumb or nonsense, even though I don't know why.

My intuition tells me that if one thread t1 is executing the second snippet of code, there's no way for another thread t2 to get there, in the middle of the code that t1 is executing. And if more threads are executing the second snippet of code, well every one of them will be working on its own anyway. But again, I'm not sure I'm saying anything sensible.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Enlico
  • 23,259
  • 6
  • 48
  • 102
  • 1
    If that fear was warranted, we could only conclude that C++ thread semantics = pretty f.cked up. (Also: +1 for humility shown asking Q.) – curiousguy Apr 01 '23 at 22:37

2 Answers2

3

So a friend asked me, can anything else, in multithreaded fallacious code, happen in the middle of std::make_shared's execution causing the same effect?

Other things can certainly happen in between std::make_shared allocating memory and creating the std::shared_ptr<Widget>. However, none of those things can cause the pointer to leak, unless something has gone seriously wrong (i.e. the program has UB).

Any exception that occurs in another thread will only interfere with the control flow in that thread and, at worst, bring down the entire process (at which point the operating system will reclaim the memory). In order to make the pointer leak, the other thread would have to corrupt the stack in the thread that is running std::make_shared, which is not possible unless it does something that produces UB.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
2

... can result in a leaked Widget if computePriority() throws in between new Widget evaluation and the call to std::shared_ptr constructor ...

That used to be a real concern in older C++ standards, however that is no longer possible since C++17, as new guarantees were introduced that will make std::shared_ptr<Widget>(new Widget) be fully executed either before or after computePriority() is ever called. They can't be interweaved anymore (see this answer to What are the evaluation order guarantees introduced by C++17?).

"Effective Modern C++" predates C++17, however it is still good advice to always use std::make_shared<T>(...) rather than std::shared_ptr<T>(new T(...)) whenever possible. For instance, std::make_shared() uses a more efficient allocation strategy then the std::shared_ptr constructor does. std::make_shared() allocates the managed object and the control block as a single memory allocation, whereas calling the constructor directly will allocate the managed object and the control block separately.

if one thread t1 is executing the second snippet of code, there's no way for another thread t2 to get there, in the middle of the code that t1 is executing.

That is not true, due to modern OS systems using pre-emptive task-switching between threads. Say, t1 is executing a piece of code on a CPU, using a given piece of memory. The OS's thread scheduler will actively interrupt t1 midway through its work at certain intervals, switching execution to t2 (or any other thread) on the same CPU. t2 (or whichever thread) could then freely modify that same piece of memory that t1 was using (ie, like assigning to a global variable that t1 reads from, etc). The OS scheduler will eventually switch back to executing t1 on the CPU, which would then continue on its work, using the now-modified memory.

This is a completely different scenario than the compiler simply evaluating function arguments in an interweaving order on a single thread.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    I think the real question is whether the other thread could somehow get access to either the `Widget` object or the `shared_ptr` in such a way that it could mess things up and cause a memory leak. I think the answer is no, unless the `Widget` constructor is doing something really dodgy (like setting a global variable pointer to point to itself or etc) – Jeremy Friesner Mar 23 '23 at 18:02
  • 1
    @JeremyFriesner all of that you just said is correct. Of course, there is always the possibility of random memory corruption occuring in another thread, where the `shared_ptr`/`Widget` just happen to be the unwitting victims. But that would be *undefined behavior* and so all bets would be off anyway. – Remy Lebeau Mar 23 '23 at 18:13
  • @RemyLebeau, but then what is the answer to the question in the title (or equivalently the one in the middle of the body)? – Enlico Mar 23 '23 at 18:26
  • @JeremyFriesner, I think that's more or less what I had in mind. – Enlico Mar 23 '23 at 18:26
  • 2
    @Enlico in *well-behaving* code, another thread cannot interfer with `std::make_shared()` and cause a leak, no – Remy Lebeau Mar 23 '23 at 18:28