Unlike most languages that have ref-counting build in, shared_ptr also doesn't provide anything to deal with cyclic dependencies, so you can end up with memory leaks.
The most important reason however is simply that you don't need it like 99% of the time, unique_ptr provides enough functionality to work just fine as a shared_ptr replacement in most situations. And in the rare cases where you really need a shared_ptr, you can just convert a unique_ptr into one.
They are quite heavily (and badly) used in some code bases (ROS). I'm planning a future article that covers shared_ptr in more details.
The surprising thing about shared pointer is that a `const shared_ptr<T>` means that you can modify the contents of T. This makes the problem, mentioned in the parent, of keeping track of who can modify the object where impossible. I've never encountered a `const shared_ptr<const T>` but that would be a better approach.
Languages with GC-managed runtimes (Java, C#, Go, Swift, et. al.) are actually significantly more performant for almost all heap-bound use cases, actually. Reference counting kinda sucks for typical code.
Swift does not have a GC managed runtime in the same sense as the other languages you listed [0], it’s basically a bunch of refcounts inserted by the compiler, with similar performance characteristics to Arc in Rust or shared_ptr in C++. (For classes, at least. Structs are value types and stack-allocated.)
[0] Yes, automatic refcounting is a form of garbage collection, but Java/C#/Go use tracing GC’s and not direct refcounting, whereas with Swift it’s more like the compiler is wrapping all objects in a shared_ptr for you, and so destruction is explicit and happens at exact points.
When a GC-managed language uses reference counting as implementation algorith, the compiler might be able to optimize the reference counting to only occur when it is unavoidable or too costly to reason about, in a similar vein to bounds checking.
When using library types for reference counting, there is no way for the compiler to implement such optimizations, unless the types are somehow tagged with compiler intrisics so that they could apply the same kind of optimizations.
Although unique ptr is zero cost after make_unique(), I avoid polluting my heap unnecessarily. I've never benchmarked this though (keeping various objects on stack vs heap as unique ptr and how that impacts memory accesses)
I'm quite junior. Appreciate anyone pointing out if anything I said doesn't make sense.
Sounds about right. Shared ownership is fairly rare though, and you often only need shared access (reference/pointer if nullable) and can provide other, more explicit, ways of managing the lifetime.
> unique ptr is zero cost after make_unique()
Kind of, but compared to the stack, it could cause caching inefficiency because your heap-allocated thing could be almost anywhere, but your stack-allocated thing is probably in the cache already.
Also if you lean in too much on shared/unique pointers for large amounts of tiny objects you'll most likely end up in a situation where Java-style garbage collection would be more efficient.
E.g. manual or semi-manual memory management is mostly about controlling the overall memory layout of your application's data to improve throughput, reducing the number of individual heap allocations is just a useful side effect.
So instead of just accessing memory, you have the cost of cross-cache coherency operations between CPU cores.
- Wrapping all objects in shared pointer is annoying. - If you stick to that convention, you have to do it on every call side, while you only have to implement RAII once. - You can enforce invariants of your class with RAII, that you can't with a plain shared_ptr - Regarding efficiency: It has the overhead of reference counting plus you have to store all objects on the heap instead of the stack. In hot loops this may hurt cache locality.
This seems like an extremely low bar.
Anyway, what use is there for C++ in 2025 aside from maintenance of legacy codebases and game engines? Off-hand I'd say C++ programmers are twice as expensive as rust programmers for the same semantics, and then you're still stuck with a C++ programmer you need to figure out how to socialize with.
I wrote this article for a few friends who have recently started working with an existing, large C++ code base. Some industries like the video game industry have also stuck to C++ (they have their reasons).
A good programmer can work within the given constraints to make a useful program — sometimes, one of those constraints is the choice of language.
The fact that lists like yours so often end up being "Look at all these C libraries" ie not actually about C++ at all is revealing. It's an endorsement of Bjarne's position that he needed that C compatibility, decades later C++ alternatives remain unpopular but it also tells you that you're never going to raise the bar this way. C++ is not a route out.
AFAIK there is no equivalent of rustls-openssl-compat for C++. The knowledge that this library (OpenSSL) is trash never spurred any C++ programmers to do better and provide the same ABI but with a C++ implementation.
Is it, though? Most mainstream languages fail to support anything resembling RAII, at least as first-class support. Do you actually have an example of a language that does a better job at resource management than C++?
> Most mainstream languages fail to support anything resembling RAII
Wikipedia says: "RAII is associated most prominently with C++, where it originated, but also Ada,[3] Vala,[4] and Rust"
> Do you actually have an example of a language that does a better job at resource management than C++?
They don't necessarily offer a pattern like RAII, but what about "try-with-resources" in Java, or "use" in Kotlin that goes with `AutoCloseable`?
And what about "using" in C#?
What about "defer" in Swift?
I find those simpler than RAII.
In terms of languages you'd actually deploy today Rust is better both at this narrow feature and more broadly.
though it isn't clear how much of rust's increased productivity is caused by being a new language where the architecture mistakes of the past decades are not slowing you down. We will need several more decades to answer that.
You're obviously trolling, but:
IME Rust attracts the same 'difficult' characters that are also attracted to C++ (because both are 'puzzle solving languages' instead of 'problem solving languages') the typical Rust coder may be even worse because of the missionary fervor and 'holier than thou' attitude.
Anecdata, but the number of times I actually encounter these missionary Rust coders (the RIIR types) is utterly dwarfed by the number of times I hear people complaining about them. The memes making fun of the insufferable rust evangelists are at least 10x as prevalent as the evangelists themselves.
We were already making use of RAII in C++ compilers for MS-DOS during the C++ARM (the C++ equivalent of K&R C book), one such example would be Turbo Vision C++ variant and Borland International Datastructures Library (BIDS).
If anything, it is a pity that 30 years later, it is still something we need to educate people about, as it isn't as adopted as it should be.
EDIT:
If this is supposed to be Modern C++, at the very least provide C++23 example in 2025, instead C++ from 2011.
Meaning import std, using a custom deleter in std::unique_ptr (it doesn't manage only heap types), making use of =default to get back compiler generated member functions, as it would do the right thing in the example for the fd handle.
One things about RAII that I find is that, a bit like inheritance, it's actually not something you have actually do nearly as much as the emphasis it gets in the first chapters of all the books implies. How often do you actually write a move constructor or even a destructor in workaday code? Most of it is done for you by the stdlib (unique_ptr, say) or libraries. Of course it needs to be understood, and understood well, but it feels like landing a plane: you have know it well and it's not unusual and certainly not wrong, but also flying is mostly doing things other than landings.
In some cases this can be optimised to the same machine code, but not always, and semantically it's not what we actually wanted anyway. In terms of RAII this means arranging that after the move the "moved from" object remembers it no longer has any resources and so when it is destroyed it won't clean up - much easier to just design the language properly.
g++ -std=c++23 -fmodules foo.cpp
Without manually generating the std module first. Until all compilers will directly accept the import keyword it won't be adopted on a large scale by regular programmers.Many projects do just fine sticking to a single compiler.
Not for everyone, but they exist nonetheless, including on FOSS world.
I think you're missing context. The term "modern C++" is used to refer to idiomatic C++ introduced in C++11. It has been that way for a couple of decades.
https://learn.microsoft.com/en-us/cpp/cpp/welcome-back-to-cp...
The article touches on the topic of move semantics and move constructors. That's modern c++. That's not your C++98 RAII.
In the same vein, "modern CMake" is also the term used to refer to the declarative style that followed the release of CMake 3, which dates back years.
The modern parts covered in the article are the move semantics making RAII more expressive and the standard library embracing the concept throughout.
I think the idea of modern is to convince everyone to give these 'new' techniques a try if they aren't already using them.
There are already plenty of memes on C++ conferences regarding that.
Modern C++ is all about template metaprogramming, concepts, move semantics, ranges, etc.
But there’s a significant cognitive load will all of that.
Better than nothing, and might be the most preferred way of doing things in C++, but it does seem dangerous to me. ;)
I would have loved it if this warning was on by default but sadly, you have to know to turn it on.
A static analisys tool can enforce the rule as well- you should have one.
unfortunately though this is one area that because c++ predates RAII it can't changethe defaults without breaking a lot of code. I am saying the problem is manageable - there is a real problem though. If you make a new language learn from this mistake.
> A careful programmer will check the return value of close(), since it is quite possible that errors on a previous write(2) operation are reported only on the final close() that releases the open file description. Failing to check the return value when closing a file may lead to silent loss of data. This can especially be observed with NFS and with disk quota.
Last time I checked, GCC’s implementation of ~ofstream() ignored failures from the underlying close().
That's why in many situations it makes sense to have an explicitly called discard() method, while the destructor is 'empty' and only checks that discard has actually been called.
Of course that also means that RAII isn't all that useful for such situations.
Would it be better if it was more subtle? Were you reading in dark or light mode?
I had the same feeling in grad school back ~15 years ago… I could pick any language for my coursework, so I picked C++ purely because it seemed an important language to learn. I was also tracking C++0x (later C++11) very carefully just for academic purposes. I’ve still never used C++ at work but I don’t regret using it in grad school, learning it gives a lot of insight into tradeoffs when designing a language, and I felt it was a lot easier to pick up Rust later on having already known C++.