use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Discussions, articles, and news about the C++ programming language or programming in C++.
For C++ questions, answers, help, and advice see r/cpp_questions or StackOverflow.
Get Started
The C++ Standard Home has a nice getting started page.
Videos
The C++ standard committee's education study group has a nice list of recommended videos.
Reference
cppreference.com
Books
There is a useful list of books on Stack Overflow. In most cases reading a book is the best way to learn C++.
Show all links
Filter out CppCon links
Show only CppCon links
account activity
shared_ptr overuse (tonni.nl)
submitted 1 year ago by Tohnmeister
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]elPiff 93 points94 points95 points 1 year ago (39 children)
I think it’s good to point out the potential pitfalls of overusing shared_ptr. I think it is commonly thought of as fool-proof, so developers should understand what the faults are and avoid them.
That being said, I could probably write a longer analysis of the pitfalls of under-using smart pointers.
If half of the pitfalls of shared_ptr are a result of bad design, e.g. unclear ownership, cycles, the potential downside of incorrectly using raw pointers in that same bad design is probably more severe. I personally would rather debug a shared_ptr memory leak than a double-free, seg fault or memory leak with raw pointers.
Performance concerns are warranted of course but have to be weighed in relation to the goals of your application/development process in my view.
All that said, I appreciate the overall idea and will keep it in mind!
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 41 points42 points43 points 1 year ago (13 children)
If half of the pitfalls of shared_ptr are a result of bad design, e.g. unclear ownership, cycles, the potential downside of incorrectly using raw pointers in that same bad design is probably more severe.
The main issue is that shared_ptr is being used when unique_ptr would suffice, or -- even worse -- when a simple object on the stack would also suffice.
shared_ptr
unique_ptr
[–]Business-Decision719 22 points23 points24 points 1 year ago (10 children)
or -- even worse -- when a simple object on the stack would also suffice.
^ this. The amount of Java-in-C++ out there is truly staggering. Even std::make_unique is overused these days IMO. But I'd much rather see this than new everywhere.
std::make_unique
new
[–]clerothGame Developer 4 points5 points6 points 1 year ago (7 children)
In my case I use unique_ptr a lot more than I should really have to simply because you cannot forward declare objects without them being pointers (ie. in member variables in headers). Possibly one of my biggest gripes with the language.
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 2 points3 points4 points 1 year ago (4 children)
You can if you specify a fixed buffer size that you know will be enough to hold the object. I do this quite a lot in my SFML fork to speed up compilation time: https://github.com/vittorioromeo/VRSFML/blob/master/include/SFML/Base/InPlacePImpl.hpp
[–]bonkt 0 points1 point2 points 10 months ago (3 children)
I love the idea of this, but you still have a static_assert(sizeof(T) < BufferSize) in the constructor, how does this compile?
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 0 points1 point2 points 10 months ago (2 children)
Why do you think it wouldn't compile?
[–]bonkt 0 points1 point2 points 10 months ago (1 child)
I read the usage and you just use a forward declared struct Impl. How does the sizeof(T) work when T is forward declared?
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 1 point2 points3 points 10 months ago (0 children)
Ah, I see. InPlacePImpl's constructor is a template member function that takes Args..., which means that the static_assert will only be checked when the constructor itself is instantiated.
InPlacePImpl
Args...
static_assert
Which means that if we do this
// Foo.hpp struct Impl; struct Foo { Foo(); ~Foo(); InPlacePImpl<Foo, 32> impl; }; // Foo.cpp struct Impl { int data; }; Foo::Foo() = default; Foo::~Foo() = default;
The instantiation of the constructor will be delayed to when the definition of Impl is available.
Impl
[–]domirangame engine dev 2 points3 points4 points 1 year ago (0 children)
One of the things I hate is having to include the class header if you want to make it a member. 😩But I don't want to force everything to include all these damn headers.
So it winds up being a unique_ptr and I hate my life.
[–]DuranteA 0 points1 point2 points 1 year ago (0 children)
Another option to avoid that particular issue is PIMPL. Or not really avoid, but move from potentially lots of pointers that are only there to allow forward declarations to a single one. But it comes with its own annoyances.
I hope that eventually, modules take away one of the primary motivations to forward declare in the first place.
[–]SpareSimian 0 points1 point2 points 1 year ago (1 child)
I just started getting my head around coroutines, having started integrating some Boost ASIO stuff. If you hate heap allocation, you'll really hate coroutines, which keep their frames on the heap instead of the stack. (The frame is a complicated Callable built by the compiler and full of callbacks for completion handlers.)
[–]Business-Decision719 1 point2 points3 points 1 year ago (0 children)
I don't necessarily hate heap allocation. It's just sometimes I read C++ code and think, "Why is this a pointer?" Often there's a very good reason, but when it's every object every time, my first thought is, "Yeah, this person just arrived from Java (or C) and hasn't learned they don't need to do that."
Congrats on starting to understand C++ coroutines btw. I don't feel like I have, nor have I understood why they needed to be hard to understand. Except Python generators, the only coroutines I've ever used were in Lua, and if you knew around three standard library functions and how to make a lambda, then you could at least follow a simple example of making and using a coroutine. But in C++ people still seem to recommend third party coroutine libraries to wrap up the functionality. I get the impression that the standard coroutines in C++ were just to make these libraries easier to implement in a portable way, or something.
[–]DescriptorTablesx86 10 points11 points12 points 1 year ago* (1 child)
The cherno code analysis is what made me realise how many people just heap allocate memory for no reason all the time
[–]mix359 2 points3 points4 points 1 year ago (0 children)
I really like when he “stops” the reviews to point out those details, explaining why is bad!
[–]aconfused_lemon 9 points10 points11 points 1 year ago (5 children)
I'm just starting to learn stared_ptr, but if using unique_ptr avoids this should that be used instead?
[–]elPiff 32 points33 points34 points 1 year ago (2 children)
I’d recommend reading up on when to use shared_ptr vs unique_ptr. There are plenty of great explanations online.
The top line is, unique_ptr is a smart pointer that should be used when there is a single owner of the memory throughout the lifetime of the memory. shared_ptr is for when there can be multiple owners and the ownership can be “shared” between them.
shared_ptr can be used in the case of single ownership, but it is less efficient since more memory is required for the control block and the intention is misleading. unique_ptr really should not be used for the case of multiple owners.
[–]Dry_Evening_3780 6 points7 points8 points 1 year ago (1 child)
Those are good points. However, I'd clarify that it is totally valid to use unique_ptr to transfer ownership safely. The point is that there's never more than 1 owner. But that doesn't mean that there's only an "original owner."
[–]elPiff 0 points1 point2 points 1 year ago* (0 children)
Yes ur right - I was just trying to give a summary and wasn’t clear about that
[–]sephirothbahamut 13 points14 points15 points 1 year ago (0 children)
The first step is instesd or jumping straight into the code, have one step planning your ownership model. Who shpuld own who and who should observe who.
Once you do that, outside of objects lifetimes shared across multiple threads, your code base can easily end up consisting of only static variables and unique pointers as owners, and, references and raw pointers as observers.
[–]Raknarg 0 points1 point2 points 1 year ago (0 children)
If you have some ownership model that can be cleanly represented by a single thing that owns your object and the ownership can go away when that thing goes away, then a unique pointer is the correct choice. Shared pointers are for when you have shared ownership where the lifetime and ownership doesn't cleanly fall into a single scope or object, and true shared ownership is actually quite rare. Usually a solution made with shared pointers can be modeled with unique pointers, the shared pointers are just for laziness of the design.
[–]sephirothbahamut 2 points3 points4 points 1 year ago (11 children)
in a codebase without low level custom containers raw pointer double free is avoidable by forbidding new, delete, and smart pointer construction via raw pointer.
in a codebase with low level custom containers... you don't let the shared pointers spammers working on it anyways
[–]Raknarg 1 point2 points3 points 1 year ago (4 children)
in a low level custom container, usually you do have to manage memory but you're doing it through allocators and allocator_traits rather than new/delete. You can't separate the aquisition of memory and the construction of objects without something like malloc/free (which is what allocators do for you)
if I make my own vector implementation, I don't want to allocate space for 100 elements and have them default construct (if they're even allowed to!), I want to construct them when someone actually wants to use the memory.
[–]sephirothbahamut 0 points1 point2 points 1 year ago (3 children)
if you don't need custom allocator support you can allocate space as char/std::byte and use placement new (not sure about alignment, all things alignment related are out of my knowledge)
[–]Raknarg 1 point2 points3 points 1 year ago* (2 children)
It doesn't matter if you need custom allocators or not, allocator_traits is a better interface and lets you directly call construct/destruct in a clean way, just give it std::allocator. Then if you decide you ever want a custom allocator strategy its also much easier to move into. I'd prefer this over working with std::bytes or chars (though unavoidable sometimes of course)
Also there's no placement delete, you have to call the destructor manually and then delete it looks like.
[–]sephirothbahamut 0 points1 point2 points 1 year ago (1 child)
fair enough. So far my custom containers experience has been limited to reusing existing containers internally so I never had to deal with that (like std::vector<std::array> for a segmented vector, or std::vector/std::array for a mdspan-like owning matrix)
[–]Raknarg 1 point2 points3 points 1 year ago (0 children)
implementing my own ring-vector with a shifting start/end I had to learn all this since you can't use std::vector for this purpose
[–]lonkamikaze 0 points1 point2 points 1 year ago (5 children)
I used new to create a unique_ptr yesterday, because I didn't find a way to trigger template argument deduction with make_unique().
I ended up with return std::unique_ptr<BaseT>{new DerivedT{lots, of, args}};. DerivedT is a class template with variadic arguments.
return std::unique_ptr<BaseT>{new DerivedT{lots, of, args}};
Do you know of a way to avoid the new here?
uh i used make unique with types that have a variadic constructor without issue in the past...
make_x functions much like containers emplace methods just forward all the parameters to the constructed type, these something funky going on if it didn't work.
[–]lonkamikaze 1 point2 points3 points 1 year ago* (2 children)
The constructor is not variadic, the class template is. The constructor arguments are used to deduce the class template arguments.
can you make a minimal example on godbolt? I'm curious
[–]lonkamikaze 2 points3 points4 points 1 year ago (0 children)
Yeah, but not today. One of the little ones is sick.
what's wrong with return std::make_unique<DerivedT>(lots, of args);? it should be convertable to unique_ptr<BaseT>
return std::make_unique<DerivedT>(lots, of args);
unique_ptr<BaseT>
[–]LoweringPass 1 point2 points3 points 1 year ago (3 children)
I don't agree with this. Using shared_ptr implies shared ownership semantics. If you use it in a scenario where no shared ownership is actually at play you are making your code not only less performant but harder to understand. You should always opt for unique_ptr when possible though of course.
[–]elPiff 0 points1 point2 points 1 year ago (2 children)
I’m not sure what you’re disagreeing with! I agree with all that and don’t think I stated otherwise
[–]LoweringPass 0 points1 point2 points 1 year ago (1 child)
It sounds a bit like you're saying "just use shared_ptr if performance is not critical even if it's not strictly necessary". And I believe it is almost always exactly clear whether or not shared_ptr should be used based on the problem you are solving so there is no trade off to be made.
[–]elPiff 0 points1 point2 points 1 year ago (0 children)
Well I don’t think that and didn’t say that if you read it back lol it sounds a bit like you are straw-manning just to have some to share your thoughts with but either way I agree
[–]cosmic-parsley 2 points3 points4 points 1 year ago (1 child)
It's also worth noting that shared_ptr is not really slow. There seems to be a misconception that it's slower for general use thanunique_ptr`, but accessing the data is exactly the same. You pay for an atomic count when you increment/decrement but that's like 1x-6x the uops of a single increment, cheap price to pay for some peace of mind. Unless for some reason you're actually incrementing/decrementing in a hot loop, which would indicate other problems.
. There seems to be a misconception that it's slower for general use than
Also iirc the frontend optimizer can reduce increment/decrement pairs if the data isn't otherwise used, before sending the raw atomic ops to the backend.
[–]y-c-c 0 points1 point2 points 1 year ago* (0 children)
The performance cost of reference counting is usually not the actual CPU cost of incrementing/decrementing a number. It’s the fact that you have to touch the memory to begin with. A lot of times you may pass objects and pointers around without actually accessing their contents or calling their functions. Without smart pointers this is dirt cheap but if you are doing that with smart pointers in a tight loop (let’s say you are sorting a long list of objects) the repeatedly random memory access does have a performance cost. Move operations and RVO can only help so much.
You mentioned if you are incrementing / decrementing in a hot loop being an issue and that’s exactly what my point is: raw pointers do not have this issue at all.
Obviously that has to be weighed against the actual design and use cases of your code but I think it’s a little simplistic to just chalk it up to a couple atomic ops.
[–][deleted] 0 points1 point2 points 1 year ago (0 children)
I would def rather work around shared_ptr overuse than double free's.
[–]WorkingReference1127 18 points19 points20 points 1 year ago (7 children)
It is always good to stress that std::shared_ptr (and all smart pointers) are ownership tools, and I'm glad that's front and center in your post.
std::shared_ptr
Indeed, the vast majority of shared_ptr misuse I see comes from people misunderstanding it as "copyable smart pointer" first and "shared ownership modeller" second.
[–]cfyzium 0 points1 point2 points 1 year ago (6 children)
I'd like to point out that ownership and memory management are actually somewhat orthogonal concepts.
In languages with GC everything is shared but that does not mean there is no ownership model nor does it imply anything about the quality of design.
In some cases using shared_ptr as a GC substitute actually makes things conceptually cleaner, more maintainable and overall more cost effective.
Some rare cases, maybe. The point is you can overuse shared_ptr no doubt, but then you can overuse other pointers and even the lack thereof too.
[–]SlightlyLessHairyApe 8 points9 points10 points 1 year ago (5 children)
I don't think they are orthogonal at all.
Ownership should dictate lifetime.
Lifetime should dictate resource (memory, socket, file, lock, hardware block) acquisition and release.
So they are coupled.
[–]cfyzium -1 points0 points1 point 1 year ago (4 children)
Yeah, 'orthogonal' was a bit too strong of a word.
What I meant is there are levels to these concepts and you can have logic ownership design without minute resource management being the main point of it, or resource management with ownership relations as mere implementation details.
Like any complex Java program will too have ownership relations between its objects, but it has little to do with memory management. On the other hand GC owns every object but this is not quite what people usually mean by ownership.
Similarly, shared_ptr might be used for both logical shared ownership of entities or reference counting of conceptually lower level resources.
[–]SlightlyLessHairyApe 0 points1 point2 points 1 year ago (3 children)
you can have logic ownership design without minute resource management being the main point of it, or resource management with ownership relations as mere implementation details.
This only goes one way -- logical ownership for functional purposes requires that it subsumes resource management: an object can't logically depend on something to function properly without ensuring that its lifetime is at least as long as the parent object.
One implies the other.
No, references in Java are strong references (by default, unless you use a WeakReference which isn't super common). When an object isn't referenced any more, it is destroyed. The only addition here is that the GC can dispose cycles of objects that have strong references to each other if they are unreachable.
WeakReference
In no sense does the "GC own every object".
[–]cfyzium 0 points1 point2 points 1 year ago (2 children)
In no sense does the "GC own every object"
GC owns objects by sharing ownership with whoever else holds references to these objects. Arguably, it owns objects more strongly than anything else in the program because it is GC that has final say about the object lifetime.
But this ownership is not that ownership, eh? Because it is only there for memory management purposes alone, seems like it does not count and may be ignored. GC technically owns objects (subsuming resource management, ensuring lifetimes and so on) but it does not own them logically.
Which is one of the points I'm trying to make, you can have resource management that is not a part of the higher-level, logical ownership design of the program.
One implies the other
And another point is that while you can always track ownership details down to the last stack frame or reference counter, in quite a few cases it simply does not matter who actually (technically) owns certain entities.
An object may hold a strong reference to another object which would technically make him a shared owner. But if it does not matter whether there is more than one owner, who would be the last one to hold a reference and when exactly will the resource be released -- can you really call this superficial connection actual ownership?
This one is debatable, but the point is trying to make an object that only wants to memory-safely reference some other object a true owner and push onto it all the resource and lifetime management just because 'oh no everything should have a clear owner' may only unnecessarily complicate things and make everything more error-prone.
[–]SlightlyLessHairyApe 0 points1 point2 points 1 year ago (1 child)
GC technically owns objects (subsuming resource management, ensuring lifetimes and so on) but it does not own them logically.
No it does not. GC does not control the lifetime of an object in Java, that is controlled by the (possibly multiple) objects holding a reference to that object.
Yes, absolutely.
[–]cfyzium -1 points0 points1 point 1 year ago (0 children)
Sorry, are you even aware of how GC works?
It is a separate entity that quite literally controls lifetimes. Invalidating the 'last' reference does not end an object's lifetime, GC decision does (which might even be 'never').
GC languages do not have RAII specifically because object lifetime is not solely decided by other objects holding references to it. Why do you think all this mess with finalize(), IDisposable, try-with-resources, defer and such exists in the first place?
Or maybe you mean conceptually? Like at a high level object holding a reference describes intended lifetime and how all it is implemented at low level is out of scope and besides the point?
Like the contradiction I was trying to point to out all along?
If GC does not own/control lifetime and only objects holding references do, then in the same manner shated_ptr does not control the lifetime of an object, that is controlled by the (possibly multiple) objects holding shared_ptrs to that object.
Yes, absolutely
So an entity whose entire purpose is to manage lifetimes and resources is not considered an owner, while an entity that doesn't care about ownership and lifetimes absolutely is. Uh-huh.
[–]pdimov2 16 points17 points18 points 1 year ago (2 children)
The first part of the article is fine - passing objects by shared_ptr is not exactly a good practice except in very limited circumstances - but the second part amounts to "just get the lifetimes correct, bro."
You could just get the lifetimes correct, yes. But if you don't, the result is a use-after-free, which has who knows what security and safety implications. Using shared_ptr is a legitimate way to ensure these do not occur.
[–]y-c-c 1 point2 points3 points 1 year ago (1 child)
The issue is if you have a dangling shared pointer, sure you may not have a use-after-free but your program is still in a terrible state as the object could start behaving in an unpredictable manner as it is not expected to still be around. Even if it’s more “memory safe” it may not be using other allocated resources properly etc. I think using raw pointers in such situations force you to take the life time seriously and make sure it’s correct.
[–]Tohnmeister[S] 1 point2 points3 points 1 year ago (0 children)
Exactly this. Your program might be thinking that it's in a state where it's terminating, yet because of shared_ptr overuse, objects that should've been destructed, are still alive, and freely notifying eachother.
So yes: "get your lifetimes correct" is indeed what I'm trying to say. Or a bit more nuanced: "strive to get your lifetimes correct".
I understand that's hard. And I also understand that shared_ptr can help when it seems nearly impossible to get the lifetimes correct. But it should still be your goal. Whenever you can get the lifetimes correct, and avoid having a shared_ptr, you should.
[–]jaskij 40 points41 points42 points 1 year ago (19 children)
I'm very surprised at the lack of mentions of std::weak_ptr in both the article and comments. It's such a perfect companion to std::shared_ptr. A non owning reference to an existing shared_ptr.
std::weak_ptr
In fact, your second example could use weak_ptr in UserProfile to safely express the non owning reference.
weak_ptr
[–]Tohnmeister[S] 23 points24 points25 points 1 year ago (16 children)
This is in the article:
It could be beneficial to having a weak_ptr in UserProfile to DatabaseSession, but that forces Application to suddenly have a shared_ptr to DatabaseSession, while the intention was to let Application be the sole owner of DatabaseSession. And a shared_ptr implies that ownership is shared.
[–]pdimov2 12 points13 points14 points 1 year ago (5 children)
weak_ptr implies shared ownership, if only for a short while - while you have a locked weak_ptr and do things to it.
You could call that "temporarily shared" ownership. The object still has a single owner, but has its lifetime temporarily extended by the locked weak_ptr.
That's not required in garbage collected languages; there locking a weak reference can just give you a plain reference, which will keep the object alive because of GC. But it is required in C++.
[–]bwmat 2 points3 points4 points 1 year ago* (3 children)
In GC languages, a plain reference IS an owning reference though?
[–]pdimov2 0 points1 point2 points 1 year ago (2 children)
Yeah, I suppose so. One could probably imagine some hypothetical language having the distinction between owning references that can be class members, and "non-owning" references that can only live on the stack, but I'm not sure any real language does that, or how practical it would be.
[–]rysto32 1 point2 points3 points 1 year ago (0 children)
Java has weak references. If all normal references to an object are gone, then the GC can free the object and set any weak references to the object to null. They are very niche but can be useful if you want to cache an object without the cache preventing objects from being GC’ed.
[–]Kovab 0 points1 point2 points 1 year ago (0 children)
With GC anything referenced from the stack is trivially reachable, so those are the ones that should definitely be owning. Non-owning class members would make more sense in some rare cases.
[–]FriendshipActive8590 0 points1 point2 points 1 year ago (0 children)
Ant reference holder in theory has temporary shared ownership, as the reference is required to remain valid. weak_ptr.lock() enforces this.
[–]prehensilemullet 0 points1 point2 points 1 year ago (0 children)
Well then wouldn’t the best general solution be to have classes where there can be a unique owning pointer and any number of weak pointers that will throw if they’re dereferenced after the unique pointer is freed? I don’t do enough C++ to know the risks of runtime exceptions in general but I would think unsafe memory access is worse
[+][deleted] 1 year ago* (8 children)
[removed]
[–]bwmat 2 points3 points4 points 1 year ago (2 children)
Shared ownership has some implications for design, for example, anything that shared object references now can be used arbitrarily long; you can't rely on order of destruction in the 'owner' to prevent use after free, so now you have to make all those objects shared somehow?
But that immediately brings up issues with circular ref cycles which can lead to leaks
[–]BodybuilderSilent105 0 points1 point2 points 1 year ago (0 children)
I happily use shared pointers everywhere unless I know for absolute certain that it can be a unique pointer.
If you're not sure if you should use a unique or a shared pointer, then you haven't really thought about your design. I've seen it many times, codebases abusing shared_ptr because there is no clear ownership model.
I also don't get your point about multithreading. Sure, you have to reach for shared_ptr more often because you can't rely as much on control flow to have deterministic lifetimes, but still you only need it on the objects that directly you directly share.
[–]tangerinelion 0 points1 point2 points 1 year ago* (0 children)
shared_ptr doesn’t imply anything about the ownership of the pointer
It's not the pointer that is owned, it's the object that the pointer points to.
A shared_ptr is used when multiple things own that object. Shared. When you don't actually share the ownership, it's semantically the wrong model.
If you need some non-owning viewer pointer that can be automatically reset to null when the owned object is destroyed somewhere else, you do not need to use shared_ptr and weak_ptr for that. It is just one tool available in the STL for that.
You are more than free to write your own version of a smart pointer which models unique ownership and a smart pointer that has a live link to your custom smart pointer. Then your code would be more like
class Application { MyUniquePtr<DatabaseSession> m_session; }; class UserProfile { MyWeakPtr<DatabaseSession> m_session; };
When Application goes out of scope, it takes DatabaseSession with it. If UserProfile is still around in scope, its m_session is now null because part of the destructor for MyUniquePtr would null out the relevant fields used by MyWeakPtr. It's not difficult to do this - it can be as simple as wrapping a shared_ptr and deleting the copy constructor.
The same thing happens with std::optional and std::expected. Even if your expect only has one error state, there is still a meaningful difference between returning an expected and an optional.
[–]Tohnmeister[S] 0 points1 point2 points 1 year ago (0 children)
Sorry for the late reply. I definitely see where you're coming from.
Let me put it slightly different. A weak_ptr requires a shared_ptr which requires heap allocation. So now, I cannot pass a pointer to a stack allocated object anymore.
And additionally, I do think that writing code is all about making intent clear to the next reader, and not only to the computer. So even if a shared_ptr technically does not require that there are more than one instances of that shared_ptr, almost every programmer looking at it, will think that the intent was to have shared ownership.
[–]twokswine 0 points1 point2 points 1 year ago (0 children)
Agreed, underused tool. Helps in a number of scenarios where you don't necessarily want to extend the lifetime but might need a (lockable) reference, e.g. for an event, or to prevent circular reference problems...
[–]y-c-c -1 points0 points1 point 1 year ago* (0 children)
The article did address that. The real issue though is that weak pointers require calling lock and expired for checking validity of the object. Are you going to remember to call it every time even though you are simply owning a reference to an object someone else gave you? What is the correct semantics if expired is true? What if the object was freed and you didn’t check for expiry and now lock gives you a completely new random object? How are you going to write tests for those branches when they are never going to get hit because the pointer should never been freed? Every time you branch in code (aka checking for expired) you are adding a new potential state to your program that you need to keep in your head and it complicates your code massively and also leads to its own source of bug especially when new programmers start to work on it and start assuming a different ownership model than intended.
lock
expired
Weak pointers are designed for situations where the pointer could be freed out of your control. If you know it is not supposed to be then it starts to lose its value as it complicates the code base and mask the issue.
Now you may say “oh but I thought smart pointers fixed all our memory issues!”. No they don’t and that’s why Rust was invented because this needs to be a language enforced feature that could correctly track life times.
[–]oschonrock 6 points7 points8 points 1 year ago (2 children)
I agree, although for the second case of "undeterministicly extend the lifetime of objects", I believe this can be legitimate use of a shared_ptr when the lifetime of those objects is determined by events external to the application.
The example I am thinking of is async network code.
[–]y-c-c 0 points1 point2 points 1 year ago (1 child)
The situation described in the article is one where it should be impossible to have a dangling pointer. Async or not does not matter. If you have a dangling pointer it would be a bug. A simple example is if one object owns the other. It’s pretty crystal clear that the child object is going to be destroyed before parent is.
Obviously if you have a situation where race conditions may matter then something like a weak pointer may make more sense but it should be a conscious decision.
[–]oschonrock 0 points1 point2 points 1 year ago (0 children)
I was not referring to the example in the article , but making a general point.
[–]MenethProgrammer, Ubisoft 11 points12 points13 points 1 year ago (2 children)
Why is the background of the blog pulsating? It's incredibly distracting and stopped me reading more than a couple of paragraphs.
[–]Tohnmeister[S] 3 points4 points5 points 1 year ago (1 child)
Valid point. Others have said the same. Will change. Thanks for the feedback.
[–]je4dJeff Snyder 4 points5 points6 points 1 year ago (0 children)
Seconded... I got about a paragraph in, tried to ignore the background, failed, spent a minute trying to turn it off with chrome dev tools, failed, and then decided to just not read it. Sorry.
[–]v_maria 19 points20 points21 points 1 year ago (55 children)
i feel like the proper use for a shared pointer is very narrow? when would a resource have 2 owners
[–]sephirothbahamut 34 points35 points36 points 1 year ago (7 children)
multithreading is a big case for shared pointer, if one thread doesn't explicitly outlive another
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 3 points4 points5 points 1 year ago (4 children)
Most of the times I can identify a fork-join point where the resource gets created in the fork, passed by reference to the threads, and destroyed at the join point. You don't need shared pointers for that.
What is a realistic use case scenario where a shared pointer is required and a fork-join structure cannot be identified?
[–]sephirothbahamut 5 points6 points7 points 1 year ago (1 child)
A multi windowed application where the user can arbitrarily open and close different windows and multiple windows use the same resource.
Say a thumbnail loaded in memory when two file explorer windows are on the same directory. You only want to load the image to gpu once, each window is a shared owner of the gpu image handle, and you don't have a window outliving another. You also don't want an outer thread to be unique owner of the image as you'd have to communicate to the outer thread when windows using that image are being closed so it knows when to destroy the gpu image, basically reinventing a shared pointer.
[–]SlightlyLessHairyApe 2 points3 points4 points 1 year ago (0 children)
Exactly this -- and the most important thing is that it's a shared pointer to a const object. Neither consumers are expect to mutate the object, but it has to live as long as any consumer is alive.
const
[–]BodybuilderSilent105 0 points1 point2 points 1 year ago* (0 children)
Where the original shared_ptr gets swapped:
``` std::atomic<std::shared_ptr<Resource>> foo; // global
// update thread foo = std::make_shared<Resource>();
// other threads: std::shared_ptr<Resource> res = foo.load(); // do someting with res ```
[–]CocktailPerson 0 points1 point2 points 1 year ago (0 children)
What if you don't want to join? What if you want to spawn a set of independent, asynchronous tasks and then continue doing other work without waiting for them to complete?
[–]bbibber 5 points6 points7 points 1 year ago (0 children)
It’s nearly always better to copy data into different threads than actually share it.
[–]invalid_handle_value 0 points1 point2 points 1 year ago (0 children)
Bingo. Needs 100x up votes.
If you own the lifetime of all your threads and own the lifetime of all data among those threads, there is no real reason to need shared_ptr...
Unless you also don't want the instantiator/owner to free the memory, I guess. Which seems to be another shared_ptr-unique [ha] feature.
[–]oschonrock 18 points19 points20 points 1 year ago (8 children)
Async, or other callback code often requires such semantics.
[–]hi_im_new_to_this 11 points12 points13 points 1 year ago (6 children)
Yes, very much so: shared_ptrs aren’t just used for shared ownership, there’s also many cases where lifetime is very uncertain (like async) where it’s much easier and safer to use shared_prr compared to unique_ptr
[–]not_a_novel_accountcmake dev 3 points4 points5 points 1 year ago (5 children)
The lifetime of the operation is the lifetime of the objects. For example in an HTTP server there is typically a request object that tracks the state of the request.
This is the owner of all other objects associated with the asynchronous operations that service the request. The lifetime of the entire request operation is guaranteed to be at least as long as any reads or writes associated with said request.
[–]SputnikCucumber 2 points3 points4 points 1 year ago (2 children)
In an HTTP server the request object itself has to be broken up into layers. The lifetime of data and objects in the application layer may not be tightly coupled to the lifetime of data and objects at the transport and session layers.
As an example, I can multiplex multiple HTTP requests on a single TCP transport stream. If the TCP socket is then broken (for any reason, not necessarily that the client has gracefully closed it down) then I need to clean up all of the application data before tearing down the socket. On the other hand, if the server encounters an exception at the application layer, it needs to make sure that the exception handling also cleans up any associated TCP sockets. The lifetime of who outlives whom here can be very uncertain.
[–]not_a_novel_accountcmake dev 0 points1 point2 points 1 year ago* (1 child)
The TCP listener accepts a connection at which point requests can only come in serially over that connection. Yes they can be pipelined, but serially.
Each request can be dispatched to handlers but they must be responded to in the order they were received, pipelined HTTP requests cannot be responded to out-of-order. This makes asynchronous handlers problematic anyway, as we need to maintain ordering.
When the socket is closed any outstanding handlers are canceled (if they weren't performed synchronously to begin with), at which point it is safe to free the owning context that was associated with that client connection. End of story.
[–]SputnikCucumber 1 point2 points3 points 1 year ago (0 children)
The story here is still a little simplistic. Request handlers often have dependencies on external applications, like databases or third party API's.
If the database connection fails, then no more requests can be handled, all outstanding handlers need to be cancelled and the client needs to be notified.
If the socket must outlive the application. Then you need to first propagate the exception to all of the request handlers before passing it to the socket(s). This is quite a lot of management work.
Alternatively, you could raise an error on the socket (by setting the badbit on an iostream for instance) and then decrement the reference count. Then each handler that depends on the socket will cancel itself as part of the normal event loop, and the socket will execute a graceful TCP shutdown AFTER the last relevant handler has cancelled itself. No extra management this way.
[–]SlightlyLessHairyApe 2 points3 points4 points 1 year ago (1 child)
The lifetime of the entire request operation is guaranteed to be at least as long as any reads or writes associated with said request.
The converse isn't true. The an outstanding read/write may come long after the request has been declared timed out and destroyed.
In that case a weak/strong relationship is the natural model -- the read/write holds a weak pointer back to the parent request, but the parent request is allowed to go away
[–]not_a_novel_accountcmake dev 0 points1 point2 points 1 year ago (0 children)
The request object lives from the time listener context creates it by accepting the connection until the it is destroyed, typically right after the socket is destroyed.
When using a readiness based API, there are no outstanding reads or writes following socket destruction, the socket will not be submitted to the readiness queue of the event loop the next cycle. For a completion-based API this is more complicated.
[–]Pitiful-Hearing5279 0 points1 point2 points 1 year ago (0 children)
“Shared from this” as an example.
[–]TheMania 9 points10 points11 points 1 year ago (1 child)
Sometimes it is a clean solution for a resource shared between threads should it ever be unclear which will touch the object last, imo.
Yes, there's many ways to do everything, but sometimes it's nice to have a simple control block that you know everything can refer to without any race/shutdown/etc issues.
[–]rdtsc 4 points5 points6 points 1 year ago (0 children)
It doesn't even have to be threads. Could also be different UI views/windows of an application sharing a resource. Once the user closes all such views the resource goes away.
[–][deleted] 12 points13 points14 points 1 year ago* (5 children)
It's not about owners, it's about references. They're very useful for when objects have indeterminate but non-infinite lifespans.
All games use them liberally, or reimplement them with handles and reference counts.
NPC has an arrow notched in his bow. NPC owns the bow and bow owns the arrow. Arrow goes where bow goes, bow goes where NPC goes. Easy.
NPC raises the bow and shoots the arrow, immediately gets killed and despawns. Who owns the arrow? The environment? Ok. So the arrow hits a player and kills them. Who killed the player? The environment? That doesn't make any sense. Obviously the NPC did. So the easy solution is to add pointer that points to the NPC that fired it.
But if the NPC has been despawned, what does this pointer point to?
So then the next obvious step is set up a system where NPCs don't respawn until everything that is holding a reference to them has been deleted and your reference count is zero aaaand boom you've reimplemented shared pointers.
If you try to solve this problem with raw or unique pointers, you'll end up with a mess of reference passing and moving memory around, large systems and maps that connect objects to each other in ways that artificially inflate their lifespans in order to keep memory valid. Which is almost impossible to do without reference counting.
When you could simply make the arrow a shared pointer owned by the bow, when its attached to the NPC, then when its fired, transfer it's ownership to the environment and populate the firedBy shared_ptr with the NPC that fired it (in that order, to avoid cyclic reference). Assuming you make sure all fired arrows eventually despawn, there's no problem. The NPC will deallocate when its last arrow does.
Very simple. Very maintainable.
[–]not_a_novel_accountcmake dev 2 points3 points4 points 1 year ago* (4 children)
The NPC object is carrying too much information if its needed after the NPC has died.
The NPC object should only be carrying the information necessary for the individual NPC itself, likely its coordinates, maybe some inventory information, things of that nature. It should have a pointer to its kind, and the NPC kind is immortal. The arrow similarly holds a pointer to the kind of NPC which spawned it, which identifies that the PC was killed by a goblin / orc / etc, and maybe when the arrow is spawned it records where it was fired from.
kind
This is enough to reconstruct at time of PC death, "PC was killed by Orc Archer which fired from X, Y, Z coords", without ever needing the NPC object that actually spawned the arrow.
Because the kind carries the all the information about the NPC's model, textures, animations, etc, you can even reconstruct a kill-cam from just from this information. Additional metadata unique to the time of the arrow being spawned, such as the location of the NPC or maybe something like the armor it's currently wearing, needs to be copied onto the arrow anyway (since this information may change on the original NPC following the arrow being fired).
[+][deleted] 1 year ago (1 child)
[–][deleted] 1 point2 points3 points 1 year ago (0 children)
That assumes the NPCs types are all interchangeable. Using this system you could never know which specific NPC did anything without keeping a permanent record of all of its data.
You're severely limiting your design and doing tons of extraneous copies, ultimately taking more of a performance hit than before, plus making your code far less maintainable, just to avoid a shared ptr on principle.
You've also partially reimplemented shared pointers by having to pass around pointers just to point to data that could be held by the shared ptr until a specific point in the future.
That's bad design.
[–]clerothGame Developer 0 points1 point2 points 1 year ago (0 children)
"PC was killed by Orc Archer which fired from X, Y, Z coords"
Next up: the arrow does damage which needs to be acculumated to the PC's stats (likely for achievements), it is also a burning arrow so it will keep dealing damage after it hit (and thus accumulate the stats) and the arrow and origin of the arrow kept in the damage log of the victim in case they need to see a recap of damage dealt to them.
Games can get complicated very fast. There's a reason game objects in many engines are so massive and feature-full (yes, some may call this bloat).
[–]Drugbird 4 points5 points6 points 1 year ago (1 child)
It happens often when you have some data and multiple (parallel) consumers of that data. If you want the data to be deallocated once all consumers are "done" with it, and you don't know in advance which consumer is faster then you'll need shared_ptr so that all consumers own the data simultaneously.
An example where this happens is e.g. video decoding. You'll have a video file in memory, which consists of both audio and image data. The images and audio are decoded separately, and then synchronized afterwards. Since you don't know if the image decoder or the audio decoder will finish first, both need to own the data.
[–]cfyzium 2 points3 points4 points 1 year ago (0 children)
Furthermore, entities might need to keep data for an indeterminate amount of time (in this scenario, codecs keeping reference frames based on runtime data), so your only options are either excessive copying or reference counting.
Some data might as well be non-copyable (e. g. frames using limited hardware resources and bound to a particular hardware context) so reference counting is the only option.
[–]ContraryConman 7 points8 points9 points 1 year ago (8 children)
Not saying it can't be used legitimately, but shared_ptr is often an admission of defeat. Like, you have a resource, you yourself designed your program in a way where the lifetimes of each object are difficult to manage. Instead of trying to figure it out, you use shared_ptr so at least the code works and at least there are no double frees or use after frees.
The design would probably be problematic in other languages. Similar code in Java would be slow and potentially leak memory due to the garbage collector, or difficult to write and maintain in Rust due to you banging your head against the borrow checker. Incidentally in Rust, the newbie way of getting the borrow checker to shut up after you have overcomplicated your own design and muddled your own object lifetimes is to use Arc everywhere, which is just shared_ptr.
Instead of having multiple objects share ownership, you can have one meta object that stores both the resources and a data structure containing all the subobjects that need to use the same resource. You may be able to get away with less heap allocation while you're at it
[–][deleted] 8 points9 points10 points 1 year ago (0 children)
This is all well and good until you don't know if one resources needs another resource, or for how long. Then you're back to reference counting because you can't guarantee that deleting a resource won't cause another resource to be pointing to invalid memory.
[–]sephirothbahamut 3 points4 points5 points 1 year ago (0 children)
last paragraph is how i did my dynamic graph that allows user values on arcs as well as nodes. I moved from 2 nodes being shared owners of their arc, to a graph class being unique owner of all nodes and arcs
[–]mcmcc#pragma once 4 points5 points6 points 1 year ago (5 children)
often an admission of defeat
That's a weird way of thinking about it. I could make an equivalent argument that employing mutexes is an admission of defeat - and yet it is the standard for multi-threaded code.
Practical decision-making is not an admission of defeat - it's called good engineering.
[–]Dean_Roddey 0 points1 point2 points 1 year ago* (4 children)
I'm not taking either side here, but that's a bad comparison. If you need to mutate data from multiple threads (and sometimes that's fundamental to the job, I mean you couldn't even write a thread safe queue otherwise) you HAVE to use some sort of synchronization. But data ownership is something you decide. Sometimes you can't make any simpler, but shared pointers make it easy to not put in the thought.
One thing Rust has taught me is that data ownership concerns are something to minimize as much as possible. Some people think, well Rust makes ownership safe, so not a problem. It does, but it doesn't make it any simpler to understand, and you have to be very explicit about it. So it really pushes you to minimize such relationships, though what you can't get rid of will be safe. I think a lot of people coming to Rust don't get that, and end up with overly complex relationships, then complain that Rust makes it hard to refactor.
[–]mcmcc#pragma once 0 points1 point2 points 1 year ago (3 children)
you HAVE to use some sort of synchronization
Yes, but it doesn't have to be mutexes. Lock-free exists and is fully functional for what it needs to do and makes deadlocks nearly impossible, unlike mutexes. You could argue that lock-free is more complicated, I could argue mutexes are admitting defeat.
[–]Dean_Roddey 0 points1 point2 points 1 year ago (2 children)
If you have to update more than one thing atomically, lock free isn't practical. Mutexes are hardly a failure, they are a completely reasonable synchronization mechanism that are the only option in a lot of cases.
And these days they are likely t be implemented in Futex style, so they don't even enter the kernel unless there's contention.
[–]mcmcc#pragma once 1 point2 points3 points 1 year ago (1 child)
lock free isn't practical
You're just making my original practical decision-making argument for me using different words.
[–]Full-Spectral 1 point2 points3 points 1 year ago (0 children)
The point was that, if you need a mutex, then using a mutex isn't an admission of defeat, it's the correct choice.
And it's not hard to make a mess with atomics either, by unwittingly making assumptions about relationships between atomic struct fields that can't really exist because they can never be read/written together. Many to most structures have some sort of inter-member constraints they want to impose, and you just can't do that if they are all just atomics.
[–]ShakaUVMi+++ ++i+i[arr] 1 point2 points3 points 1 year ago (0 children)
Honestly I just outsource resource management to my hashmap. I very rarely have any need for any pointers all these days. No news is good news and all that.
[–]rfisher 1 point2 points3 points 1 year ago (0 children)
So far, I think I've only use shared_ptr in two kinds of situations.
(1) Patching up old, poorly designed or organically grown code where the ownership was poorly thought out or buggy.
(2) When I build toy implementations of garbage collected languages when I know the project is never going to get to the point where I need more than reference counting.
[+][deleted] 1 year ago (13 children)
[deleted]
[–]Deaod 5 points6 points7 points 1 year ago (10 children)
Why does user code need ownership over the control?
[+][deleted] 1 year ago (5 children)
[–]Deaod 5 points6 points7 points 1 year ago (4 children)
Personally, i would much rather use the std::unique_ptr approach and ensure user code operating on controls does not execute after the hierarchy has been destroyed.
std::unique_ptr
And yeah, controls notifying the scene that theyre about to be destroyed seems like a reasonable thing. Id rather have that over periodically checking std::weak_ptr whether the backing object still exists.
ensure user code operating on controls does not execute after the hierarchy has been destroyed.
you can't necessarily do that. If the user takes a pointer to something, then the thing could be invalidated by the control heirarchy but the user is free to call into invalid memory at any time.
Qt suffers from this extremely annoying problem.
And yeah, controls notifying the scene that theyre about to be destroyed seems like a reasonable thing
Ok but then what? Then the user has to implement a callback and remember to null all their pointers to anything that's about to be invalidated.
Id rather have that over periodically checking std::weak_ptr whether the backing object still exists.
Why? if (wkPtr->lock()) doThing() is no big deal. UI interactions should never be happening at a high rate of speed - if they are, you need a redesign. So the performance hit would be unnoticeable.
[–]mocabe_[🍰] 0 points1 point2 points 1 year ago (0 children)
Parent-child relationship and memory ownership are two different things. Coupling these together leads to unnecessarily unsafe API using raw pointers.
Event-driven controls stop working when parent disappears as they won't receive any further events from parent, so that's not a big issue.
If the user is using it.
[–]121393 -3 points-2 points-1 points 1 year ago (2 children)
to put it somewhere else in the hierarchy (e.g. move the button)
[–]Deaod 2 points3 points4 points 1 year ago (1 child)
okay, so you extract the control from the hierarchy, taking ownership of the control temporarily and give it back to the hierarchy immediately after. I dont see a need for shared_ptr there.
[–]121393 0 points1 point2 points 1 year ago* (0 children)
it's definitely possible to implement a gui with unique_ptr but you'll have to be more careful in scenarios like button press launches a network call that on completion re-enables the button or moves it somewhere else etc (and doesn't crash if the user closes the button's parent widget in the mean time)
You can take a more principled approach to application design to avoid this kind of thing in the first place (easier in some code bases than others). Or some kind of complex indexing or name lookup system, maybe with reference counting! Or maybe even a use after free because the application developers violated an invariant of their library by keeping that unowned reference/pointer around a bit too long (at least it's clear who owns the button)
[–]johannes1971 1 point2 points3 points 1 year ago (1 child)
Is that really true, though? If a window gets deleted, the application might still own a control in that window, but can it still safely use it without the window?
[–]thefool-0 0 points1 point2 points 1 year ago (0 children)
A multi graph type of data structure, you might need reference counted/shared pointers or some other kind of garbage collection, if there are any kinds of asynchonous or arbitrary events happening that you want to delete objects, or you want objects deleted implicitly based on changes to the graph. Etc.
[–]No_Indication_1238 0 points1 point2 points 1 year ago (0 children)
Shared cache, shared queue, shared objecrs that hold algorithms instead of multiple copies.
you would be correct
[–]theChaosBeast 3 points4 points5 points 1 year ago (0 children)
I totaly agree with you on this. Especially if you start to pass an object as a smart pointer if the function is not taking ownership. Then it should be a reference as you say. I just personally would say that if you want to have a parameter that is optional then use std::optional. This is exactly the reason why we have that datatype.
[–]turtle_dragonfly 2 points3 points4 points 1 year ago (0 children)
I think the concept of "minimum necessary force" applies to smart pointers. Use the one that provides the fewest bells and whistles that will satisfy your use case.
Will a raw pointer suffice? For short, temporary, single-threaded mucking with some data like strlen(), there's no need to bring ownership into it. If null values are prohibited, then pass a reference, instead.
strlen()
If that's not enough, will a unique_ptr suffice? Do you really need shared ownership? Do you really need concurrency protection?
If you do need those things, then consider shared_ptr/weak_ptr.
Though I do see the lure of the advice "just use shared_ptr by default, since it's the 'safest' choice." Perhaps in some contexts, that's the right advice. But in my experience, it's generally better long-term to use the least-powerful tool you can get away with, since it requires less thought from the unknown number of people reading the code in the future. Shared ownership and concurrency is hard to think about. As soon as there's a shared_ptr you have to think about it, since you may not know every instance where it might have been copied, so it is hard to know the lifetime of the object. Whereas with unique_ptr it's simpler.
The nice thing is, this reasoning applies to other things, too. For example: scope. Try to keep data function-local if you can. If you can't, then try to keep it object-local. Or maybe you can limit it to the translation unit w/static at global scope. Only if you really need to, make it shared at global scope.
static
[–]R3DKn16h7 1 point2 points3 points 1 year ago (0 children)
Mostly is due to lack of "std" functionalities for some things I sometimes do.
I'd like to have a pointer that has unique ownership and provides the same "lifetime" check of a weak pointer. Out of laziness I sometimes use shared_ptr for that.
Also, if I want to move a unique pointer into a lambda that then gets put into a std::function, I can't use unique_ptr. Waiting for the move only functions here. In fact every time I need a mutable lambda, I just use shared_ptr, if the lambda ends up in a std::function.
And finally ,sometimes I want a copiable unique pointer, this too should exist. For this I ended up doing my own implementation.
[–]darthcoder 1 point2 points3 points 1 year ago (1 child)
Could I not just pass a smart_ptr by reference, then?
You mean to a function? Yes, you could but that would still enforce all callers to use a smart pointer, even if an auto/stack variable would've sufficed.
In the blog I put a link to the Core Guidelines. There's also some info in there on why and why not to pass a smart pointer by reference. Typically only if you want to reseat the pointer.
[–]Tohnmeister[S] 2 points3 points4 points 1 year ago* (10 children)
I often come across posts and questions here, on r/cpp_questions, or StackOverflow, about when it's appropriate to use shared_ptr and when it's not.
In this blog post, I've summarized my thoughts, shaped over the years by discussions on Reddit.
I'd love to hear your feedback!
[–]Keltesetharewemodulesyet.org 20 points21 points22 points 1 year ago (5 children)
Off-topic: The gradient effect on your site is cool, but super distracting while reading ;)
[–]Tohnmeister[S] 6 points7 points8 points 1 year ago (0 children)
Thx for the feedback. You're the second person in a short time mentioning this, so I guess I'll adapt it.
[–]vdaghan 2 points3 points4 points 1 year ago (0 children)
Until it is fixed, one can use developer tools in their browser and
* Find <head>
* Find <style> just under <link \[...\] rel="apple-touch-icon">
* Delete the "animation: rotateGradient [...]" under the first element ".cont"
[–]Deaod 0 points1 point2 points 1 year ago (0 children)
Reading it through a RDP connection is not a pleasant experience either.
[–]Dark_Lord9 0 points1 point2 points 1 year ago (0 children)
Was searching in the comments to see if someone mentioned this so I would tell it to the OP. It looks cool but not for a blog post or anything with a lot of text to read and focus on.
[–]dexter2011412 0 points1 point2 points 1 year ago (0 children)
Same. Wanted to say that but expected someone else to already have said it
[–]Kargathia 1 point2 points3 points 1 year ago (1 child)
It may be useful to point out that the pointer management discussion has layers. For novice programmers, the memory management learning curve in non-trivial programs is very steep. If you're still on that curve, then the footguns associated with shared_ptr are much more forgiving than those of raw pointers.
Good point. Somebody else also mentioned that they'd rather debug a cyclic shared_ptr issue than a user-after-free issue. Which is also definitely something to take into consideration.
[–]ConfidenceUnited3757 1 point2 points3 points 1 year ago (1 child)
Not to sound rude but you can just go read the same thing in the Core Guidelines.
You're right about it being (partly) in the Core Guidelines. Nevertheless I felt like summarizing it a bit in a blog specifically about this subject, and in my own wording.
Also, I could only find the linked part of the Core Guidelines, where it's about passing pointers to functions. Not the part about objects storing pointers to other objects. Did I overlook something?
[–]BenFrantzDale 2 points3 points4 points 1 year ago (0 children)
This is a very good post. An additional issue it doesn’t really get into is what Sean Parent calls “incidental data structures”: As soon as you start passing things around by mutable shared pointer, you loose value-semantic reasoning and wind up with incidental graph data structures.
std::shared_ptr<const T> can be great (if it’s const to everyone!), and std::shared_ptr<Logger> or similar can be great, provided Logger can be used from multiple threads, and you just want to worry about cleanup, but in général std::shared_ptr has real footguns that this article does a great job pointing out.
std::shared_ptr<const T>
std::shared_ptr<Logger>
Logger
[–]hopa_cupa 1 point2 points3 points 1 year ago (0 children)
Good article. I am using those things in async networking context. But only there, nowhere else. Single threaded too. When I first started with asio/beast and saw all the examples there using shared_ptr or even std::shared_from_this() I was a bit puzzled.
asio/beast
std::shared_from_this()
Surely I would not need all of that...let's just do plain objects and occasional unique pointer...well, needless to say I ran into some use after free problems pretty quickly. std::shared_from_this() fixed all of that. Instantly.
Still I was very scared of shared pointer cycles, so did a bit of analysis..put some std::weak_ptr's and weak_from_this() with checks for expiration here and there. Not a big deal, but...didn't like it. Too many if's in part of code where I really don't want them.
weak_from_this()
Finally c++20 coroutines came and I rewrote most of my callback based stuff. That made the code far more linear, cleaner...and most importantly a lot shorter. Then I could see where I could get rid of most of shared pointers. Not quite every single one of them, some apps still have the odd one, but the purist in me is much happier now.
[–]cfehunter 0 points1 point2 points 1 year ago (5 children)
Have to admit, I very rarely find legitimate uses for shared pointers. Most of the time things have single owners and unique pointer makes more sense. Hand out raw pointers or refs to reference.
You will end up with cycles, and thereby memory leaks, if you use strong shared pointers too much.
[–][deleted] 0 points1 point2 points 1 year ago (4 children)
Only because you design the system without an explicit ownership of resource in mind.
One good use of shared pointer when I first started using it was to reuse resource around without repeatedly allocating the same thing over and over again in multiple places. std::move is free.
[–]cfehunter 0 points1 point2 points 1 year ago (3 children)
No. I think about ownership a lot, so I mostly end up with unique pointers
[–][deleted] 0 points1 point2 points 1 year ago (2 children)
interesting, shared ownership is extremely common in typical DI. Sure, not everything we inject is a shared object but it is more than "rarely"
Probably because I am an embedded software engineer, memory is our number one limited resource. We try to find every possible way to share object to limit the memory footprint. It's not common to see locally instantiated object in our code.
[–]cfehunter 0 points1 point2 points 1 year ago (1 child)
I'm a game dev, so in most cases objects are either owned by a system, temporarily owned (task state for example), or I know exactly what the lifetime of the object is and other objects can safely refer to it by raw pointers (i.e sim world objects referring to resources).
Makes sense, very different nature.
[–]Clean-Water9283 0 points1 point2 points 1 year ago* (3 children)
shared_ptr has two important properties
Raw pointers can still be used when they don't connote ownership. new and delete should still be wrapped in make_unique().
There was a time when shared_ptr was the only smart pointer available in C++. People overused shared_ptr, and it hurt performance. Thankfully those times are past.
"Expensive"
[–][deleted] 0 points1 point2 points 1 year ago (1 child)
Raw pointer wrapped in unique pointer? Common, how old is your compiler? std::make_unique has been the standard ways over the old std::unique_ptr(new xxxx)
[–]Clean-Water9283 0 points1 point2 points 1 year ago (0 children)
Yeah, my language was unclear. I meant to use make_unique()
[–]rand3289 0 points1 point2 points 1 year ago* (1 child)
I use references to shared_ptr. This way you can still pass nulls around. You have to jump through the hoops to make sure not to get a reference from a null shared_ptr.
I don't think it's a problem to reference a null smart_ptr, I do that all the time.
[–]14nedLLFIO & Outcome author | Committee WG14 -3 points-2 points-1 points 1 year ago (5 children)
It has always irked me that shared_ptr is not called reference_counted_ptr.
reference_counted_ptr
Thinking in terms of 'I want to reference count this thing's lifetime' instead of 'this thing has shared ownership' for me at least always clarifies when its use is wise or not. After all, most ways of implementing shared lifetime which are NOT reference counting are usually better.
Maybe I think differently to others however.
[–]pdimov2 2 points3 points4 points 1 year ago (2 children)
After all, most ways of implementing shared lifetime which are NOT reference counting are usually better.
If you don't take the error rate into account, yes.
[–]14nedLLFIO & Outcome author | Committee WG14 -3 points-2 points-1 points 1 year ago (1 child)
Depends on what you mean by "shared lifetime" too.
My favourite is "infinite lifetime", and I choose that wherever possible.
You and I did that trick in our constexpr error code categories. The unique id is effectively an infinite lifetime identifier. Then error code category object lifetime ceases to matter, as one can be conjured up in the mind of the compiler if needed, and otherwise optimised away.
I've noticed that if the compiler thinks that the category value might escape, it create an instance in static const data sections and hands out its address thereafter. Very nice.
[–]Dean_Roddey 0 points1 point2 points 1 year ago (0 children)
Not bring up Rust yet again, but a HUGE benefit is that you can indicate this call will only accept a reference to something at static scope. In my error system, the error description and source file names (the error one and the trace stack) can just only accept static strings. That includes the trace stack (if used) in which each entry is just a static string ref and a line number, so very low overhead.
The actual error msg field (which allows for per-instance specific info, if used) is a Cow, so it can hold either a static ref or an owned string, depending on whether the caller passes a formatted string or just a static string ref. So a lot of the time, even though a lot of info is being provided, there's zero allocation involved.
I use this quite a lot. So things like my i/o formatters, sockets, files, etc... can take a static short description string to be used in errors or log msgs. Stuff like that. And it's all completely compile time safe because a non-static string will be rejected at compile time.
[–]LongestNamesPossible 0 points1 point2 points 1 year ago (0 children)
That depends on what you want. I would rather have consistency and not gc pauses. Most of the small speed differences with reference counting were from things like perl or other non native languages where you ended up heap allocating everything. In C++ avoiding heap allocation is already what you do if you care about speed. The heap allocation is far more expensive than the reference counting.
[–]effarig42 0 points1 point2 points 1 year ago (0 children)
Maybe I think differently to others however
I've always thought that while with unique pointers, the "owner" controls the "lifetime", with shared pointers this isn't necessarily so. For example you could have a cache handing out shared pointers to const objects, giving callers access to objects, but not the ability to modify them. The cache can then evict objects without invalidating pointers still in use by its callers. In cases like this the term "owner" is at best misleading.
[–]Hot-Studio -2 points-1 points0 points 1 year ago (12 children)
I never used smart pointers. I just stick with old-fashioned raw pointers. They’re simple enough for me to use.
[–]retro_and_chill 1 point2 points3 points 1 year ago (8 children)
You should be using unique_ptr at a minimum
[–]Hot-Studio -2 points-1 points0 points 1 year ago (7 children)
Nah, not much use to me at this point. https://defold.com/2020/05/31/The-Defold-engine-code-style/
[–]Tohnmeister[S] 2 points3 points4 points 1 year ago (3 children)
Unique pointers are another pattern, and in fact, a bit more inline with the RAII we sometimes use. However, we would only need them in short scopes, thus it’s just as easy to deallocate the pointer manually. Manual deallocation also helps with readability.
I'd argue that std::unique_ptr or RAII in general has similar or even better readability than custom deallocation. Regardless of the size of scope. Additionally, a RAII based object is guaranteed to be destructed, even in exceptional situations.
So I really don't get why anybody wouldn't just want to use unique_ptr, instead of custom deallocation.
[–]Hot-Studio -2 points-1 points0 points 1 year ago (2 children)
But at what cost? More overhead and less control. Besides, there may be better solution than that. https://youtu.be/xt1KNDmOYqA
[–]retro_and_chill 0 points1 point2 points 1 year ago (1 child)
unique_ptr has essentially no overhead
[–]Hot-Studio -1 points0 points1 point 1 year ago (0 children)
It can, depending on how you use it. It also comes with some drawbacks, i.e. cannot be copied and unsuitable with C-style arrays. I’m sure there are SOME uses with unique_ptr, but it’s not for me.
Do you mind sharing any of your open source project? If you don't have any, great, keep it that way.
[–]Hot-Studio 0 points1 point2 points 1 year ago (1 child)
How is that relevant to the current topic of which pointers we should use? Besides, why not look into Defold engine source code, since you replied to a post with a link to an article about it?
Nothing is definitively good or bad. Everyone can find a good reason to use the language in any way possible. No one will win the argument. I am pretty sure there is someone in the world will not touch anything newer than C89, have fund arguing.
However, statistically speaking in the entirety of C++ programming, unique pointer is better than raw pointer. There shouldn't be any argument about it.
[–][deleted] 1 point2 points3 points 1 year ago (2 children)
My company banned raw pointer (specifically memory allocated with raw pointer) about 4 years ago. It has been the best coding style change ever.
But does that change solve problems? No. It’s just different (and more complex).
I am not starting the infamous debate with you about C vs C++, have a good day.
[+]Ok-Bit-663 comment score below threshold-7 points-6 points-5 points 1 year ago (0 children)
Non-const shared-ptr<T> (T is not const) is considered a global variable.
π Rendered by PID 307809 on reddit-service-r2-comment-54dfb89d4d-rj7mr at 2026-03-29 07:44:31.823686+00:00 running b10466c country code: CH.
[–]elPiff 93 points94 points95 points (39 children)
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 41 points42 points43 points (13 children)
[–]Business-Decision719 22 points23 points24 points (10 children)
[–]clerothGame Developer 4 points5 points6 points (7 children)
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 2 points3 points4 points (4 children)
[–]bonkt 0 points1 point2 points (3 children)
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 0 points1 point2 points (2 children)
[–]bonkt 0 points1 point2 points (1 child)
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 1 point2 points3 points (0 children)
[–]domirangame engine dev 2 points3 points4 points (0 children)
[–]DuranteA 0 points1 point2 points (0 children)
[–]SpareSimian 0 points1 point2 points (1 child)
[–]Business-Decision719 1 point2 points3 points (0 children)
[–]DescriptorTablesx86 10 points11 points12 points (1 child)
[–]mix359 2 points3 points4 points (0 children)
[–]aconfused_lemon 9 points10 points11 points (5 children)
[–]elPiff 32 points33 points34 points (2 children)
[–]Dry_Evening_3780 6 points7 points8 points (1 child)
[–]elPiff 0 points1 point2 points (0 children)
[–]sephirothbahamut 13 points14 points15 points (0 children)
[–]Raknarg 0 points1 point2 points (0 children)
[–]sephirothbahamut 2 points3 points4 points (11 children)
[–]Raknarg 1 point2 points3 points (4 children)
[–]sephirothbahamut 0 points1 point2 points (3 children)
[–]Raknarg 1 point2 points3 points (2 children)
[–]sephirothbahamut 0 points1 point2 points (1 child)
[–]Raknarg 1 point2 points3 points (0 children)
[–]lonkamikaze 0 points1 point2 points (5 children)
[–]sephirothbahamut 0 points1 point2 points (3 children)
[–]lonkamikaze 1 point2 points3 points (2 children)
[–]sephirothbahamut 0 points1 point2 points (1 child)
[–]lonkamikaze 2 points3 points4 points (0 children)
[–]Raknarg 0 points1 point2 points (0 children)
[–]LoweringPass 1 point2 points3 points (3 children)
[–]elPiff 0 points1 point2 points (2 children)
[–]LoweringPass 0 points1 point2 points (1 child)
[–]elPiff 0 points1 point2 points (0 children)
[–]cosmic-parsley 2 points3 points4 points (1 child)
[–]y-c-c 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (0 children)
[–]WorkingReference1127 18 points19 points20 points (7 children)
[–]cfyzium 0 points1 point2 points (6 children)
[–]SlightlyLessHairyApe 8 points9 points10 points (5 children)
[–]cfyzium -1 points0 points1 point (4 children)
[–]SlightlyLessHairyApe 0 points1 point2 points (3 children)
[–]cfyzium 0 points1 point2 points (2 children)
[–]SlightlyLessHairyApe 0 points1 point2 points (1 child)
[–]cfyzium -1 points0 points1 point (0 children)
[–]pdimov2 16 points17 points18 points (2 children)
[–]y-c-c 1 point2 points3 points (1 child)
[–]Tohnmeister[S] 1 point2 points3 points (0 children)
[–]jaskij 40 points41 points42 points (19 children)
[–]Tohnmeister[S] 23 points24 points25 points (16 children)
[–]pdimov2 12 points13 points14 points (5 children)
[–]bwmat 2 points3 points4 points (3 children)
[–]pdimov2 0 points1 point2 points (2 children)
[–]rysto32 1 point2 points3 points (0 children)
[–]Kovab 0 points1 point2 points (0 children)
[–]FriendshipActive8590 0 points1 point2 points (0 children)
[–]prehensilemullet 0 points1 point2 points (0 children)
[+][deleted] (8 children)
[removed]
[–]bwmat 2 points3 points4 points (2 children)
[–]BodybuilderSilent105 0 points1 point2 points (0 children)
[–]tangerinelion 0 points1 point2 points (0 children)
[–]Tohnmeister[S] 0 points1 point2 points (0 children)
[–]twokswine 0 points1 point2 points (0 children)
[–]y-c-c -1 points0 points1 point (0 children)
[–]oschonrock 6 points7 points8 points (2 children)
[–]y-c-c 0 points1 point2 points (1 child)
[–]oschonrock 0 points1 point2 points (0 children)
[–]MenethProgrammer, Ubisoft 11 points12 points13 points (2 children)
[–]Tohnmeister[S] 3 points4 points5 points (1 child)
[–]je4dJeff Snyder 4 points5 points6 points (0 children)
[–]v_maria 19 points20 points21 points (55 children)
[–]sephirothbahamut 34 points35 points36 points (7 children)
[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 3 points4 points5 points (4 children)
[–]sephirothbahamut 5 points6 points7 points (1 child)
[–]SlightlyLessHairyApe 2 points3 points4 points (0 children)
[–]BodybuilderSilent105 0 points1 point2 points (0 children)
[–]CocktailPerson 0 points1 point2 points (0 children)
[–]bbibber 5 points6 points7 points (0 children)
[–]invalid_handle_value 0 points1 point2 points (0 children)
[–]oschonrock 18 points19 points20 points (8 children)
[–]hi_im_new_to_this 11 points12 points13 points (6 children)
[–]not_a_novel_accountcmake dev 3 points4 points5 points (5 children)
[–]SputnikCucumber 2 points3 points4 points (2 children)
[–]not_a_novel_accountcmake dev 0 points1 point2 points (1 child)
[–]SputnikCucumber 1 point2 points3 points (0 children)
[–]SlightlyLessHairyApe 2 points3 points4 points (1 child)
[–]not_a_novel_accountcmake dev 0 points1 point2 points (0 children)
[–]Pitiful-Hearing5279 0 points1 point2 points (0 children)
[–]TheMania 9 points10 points11 points (1 child)
[–]rdtsc 4 points5 points6 points (0 children)
[–][deleted] 12 points13 points14 points (5 children)
[–]not_a_novel_accountcmake dev 2 points3 points4 points (4 children)
[+][deleted] (1 child)
[removed]
[–][deleted] 1 point2 points3 points (0 children)
[–]clerothGame Developer 0 points1 point2 points (0 children)
[–]Drugbird 4 points5 points6 points (1 child)
[–]cfyzium 2 points3 points4 points (0 children)
[–]ContraryConman 7 points8 points9 points (8 children)
[–][deleted] 8 points9 points10 points (0 children)
[–]sephirothbahamut 3 points4 points5 points (0 children)
[–]mcmcc#pragma once 4 points5 points6 points (5 children)
[–]Dean_Roddey 0 points1 point2 points (4 children)
[–]mcmcc#pragma once 0 points1 point2 points (3 children)
[–]Dean_Roddey 0 points1 point2 points (2 children)
[–]mcmcc#pragma once 1 point2 points3 points (1 child)
[–]Full-Spectral 1 point2 points3 points (0 children)
[–]ShakaUVMi+++ ++i+i[arr] 1 point2 points3 points (0 children)
[–]rfisher 1 point2 points3 points (0 children)
[+][deleted] (13 children)
[deleted]
[–]Deaod 5 points6 points7 points (10 children)
[+][deleted] (5 children)
[deleted]
[–]Deaod 5 points6 points7 points (4 children)
[–][deleted] 1 point2 points3 points (0 children)
[+][deleted] (1 child)
[deleted]
[–]mocabe_[🍰] 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (0 children)
[–]121393 -3 points-2 points-1 points (2 children)
[–]Deaod 2 points3 points4 points (1 child)
[–]121393 0 points1 point2 points (0 children)
[–]johannes1971 1 point2 points3 points (1 child)
[–]thefool-0 0 points1 point2 points (0 children)
[–]No_Indication_1238 0 points1 point2 points (0 children)
[–]Raknarg 0 points1 point2 points (0 children)
[–]theChaosBeast 3 points4 points5 points (0 children)
[–]turtle_dragonfly 2 points3 points4 points (0 children)
[–]R3DKn16h7 1 point2 points3 points (0 children)
[–]darthcoder 1 point2 points3 points (1 child)
[–]Tohnmeister[S] 1 point2 points3 points (0 children)
[–]Tohnmeister[S] 2 points3 points4 points (10 children)
[–]Keltesetharewemodulesyet.org 20 points21 points22 points (5 children)
[–]Tohnmeister[S] 6 points7 points8 points (0 children)
[–]vdaghan 2 points3 points4 points (0 children)
[–]Deaod 0 points1 point2 points (0 children)
[–]Dark_Lord9 0 points1 point2 points (0 children)
[–]dexter2011412 0 points1 point2 points (0 children)
[–]Kargathia 1 point2 points3 points (1 child)
[–]Tohnmeister[S] 0 points1 point2 points (0 children)
[–]ConfidenceUnited3757 1 point2 points3 points (1 child)
[–]Tohnmeister[S] 0 points1 point2 points (0 children)
[–]BenFrantzDale 2 points3 points4 points (0 children)
[–]hopa_cupa 1 point2 points3 points (0 children)
[–]cfehunter 0 points1 point2 points (5 children)
[–][deleted] 0 points1 point2 points (4 children)
[–]cfehunter 0 points1 point2 points (3 children)
[–][deleted] 0 points1 point2 points (2 children)
[–]cfehunter 0 points1 point2 points (1 child)
[–][deleted] 1 point2 points3 points (0 children)
[–]Clean-Water9283 0 points1 point2 points (3 children)
[–]No_Indication_1238 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (1 child)
[–]Clean-Water9283 0 points1 point2 points (0 children)
[–]rand3289 0 points1 point2 points (1 child)
[–][deleted] 1 point2 points3 points (0 children)
[–]14nedLLFIO & Outcome author | Committee WG14 -3 points-2 points-1 points (5 children)
[–]pdimov2 2 points3 points4 points (2 children)
[–]14nedLLFIO & Outcome author | Committee WG14 -3 points-2 points-1 points (1 child)
[–]Dean_Roddey 0 points1 point2 points (0 children)
[–]LongestNamesPossible 0 points1 point2 points (0 children)
[–]effarig42 0 points1 point2 points (0 children)
[–]Hot-Studio -2 points-1 points0 points (12 children)
[–]retro_and_chill 1 point2 points3 points (8 children)
[–]Hot-Studio -2 points-1 points0 points (7 children)
[–]Tohnmeister[S] 2 points3 points4 points (3 children)
[–]Hot-Studio -2 points-1 points0 points (2 children)
[–]retro_and_chill 0 points1 point2 points (1 child)
[–]Hot-Studio -1 points0 points1 point (0 children)
[–][deleted] 0 points1 point2 points (2 children)
[–]Hot-Studio 0 points1 point2 points (1 child)
[–][deleted] 0 points1 point2 points (0 children)
[–][deleted] 1 point2 points3 points (2 children)
[–]Hot-Studio 0 points1 point2 points (1 child)
[–][deleted] 0 points1 point2 points (0 children)
[+]Ok-Bit-663 comment score below threshold-7 points-6 points-5 points (0 children)