Better than nothing, and might be the most preferred way of doing things in C++, but it does seem dangerous to me. ;)
What I got frustrated with in C/C++ is the insanity of each and every third-party library redefining every type. I understand the history and reasoning behind this, but it's one of those things that ought to have been fixed in the early 2000s, not decades later when it's too late.
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.
The Design and Evolution of C++ is quite clear on how this process came to be, during C++ design as language.
And by the time of Windows 3.x with OWL, Apple AppFramework, OS/2 CSet++, Motif++, they were used all over the place.
Easy to check those surviving manuals.
Also in 1995, the C++ lecture material at the university, back when everyone was implementing their own personal standard library, already discussed RAII design.
To note that even now there are plenty of universities that fail on their approach on how to teach C++, hence the Kate Gregory's talk aptly named "Stop teaching C" in the context of teaching C++.
I would have loved it if this warning was on by default but sadly, you have to know to turn it on.
An easy start is to use `-Wall -Wextra -Wpedantic` for gcc and clang compilers. I would now add `-Wdeprecated-copy-dtor` to the list but I only learned about it writing this article.
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.
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.
Aren’t refcounts in shared_ptr done as part of the copy constructor and destructor? It seems like vanilla copy elision would count as optimizing away the refcounts.
All of them have to do counter booking, value count and accessors, also possibly handle weak_ptr booking.
Plus how it actually works isn't part of ISO C++, so you're betting your luck on how a specific C++ is actually going to implement it, and possible issues when compiler versions change, same compiler or to another one.
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.
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.
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.
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.
- 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.
Would it be better if it was more subtle? Were you reading in dark or light mode?
You might have to control+f5 to see the changes, I had to.
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.
Modern C++ is all about template metaprogramming, concepts, move semantics, ranges, etc.
But there’s a significant cognitive load will all of that.
Or even articles in The C/C++ Users Journal, Dr Dobbs, PC Techniques, Microsoft Systems Journal.
Unfortunately many that write C with C++ compilers, only started paying attention around C++11.
Like, modern architecture, fashion, cuisine all change over time.
Why would “Modern C++” refer to a fixed point in time somewhere in the 90s?
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.
Postmodern C++?
I would be very sad to have to go back to C++98, it would feel like a different language — I would only be inconvenienced to go back to C++11. Maybe ranges changes that ;-).
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.
I have been keeping my C++ skills up to date since 1993.
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.
clang++ -std=c++23 fname.cpp
(I know you can use Cmake to solve it - but I want a simple solution something that can be shown to a junior programmer.)> 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.
With an explicit deinit() method you'd basically get an object that's in an deinitialized, but not destructed state (quite similar to the state a moved-from object is in), the object still exists in memory and can react with proper errors to attempts to access.
A shared_ptr to an object with a deinit() is rather like a weak_ptr in that someone else can "delete" it, and you should check if it's really there, except it can still have some information about things like deinit failures rather than just telling you that the object is deleted.
File streams, say, do this by expecting users to close() the file themselves if they care if exceptions happen, but if you destruct without doing that first, any exceptions are caught and don't make it out of the destructor, so they're just gone and you'll never know.
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++.
growlNark•1d ago
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.
motorest•1d ago
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++?
asyx•1d ago
motorest•1d ago
This assertion makes no sense. RAII is not defined by whether you use factory functions or special member functions to initialize an object. In fact, RAII is only incidentally related ro memory management. RAII is a strategy to manage any and all types of resources, ranging from memory management to even files and TCP connections, by leveraging assurances that the runtime provides regarding scopes and object life cycles. None of this changes if you employ factory functions to initialize resources.
palata•1d ago
> 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.
pjmlp•1d ago
palata•1d ago
Not criticising, really trying to understand :-).
pjmlp•1d ago
So in languages like Rust, D, Ada, Swift, C++, the compiler will do the rest unless you go out of your way to avoid the call to take place, like placing a value type on the heap using plain pointers.
With the other approach, even if you implement IDisposable, AutoCloseable, ContextManager and similar, you have to remember to manually write the code pattern that takes care of calling close(), or whatever the method/function happens to be called.
In languages with good support for FP patterns, like trailing lambdas, currying and such, there is another pattern, that is much safer, in case you don't want a static analysis tool to track resource usage, the with pattern.
You do something like withDBConnection connection (fun db -> all related db operations).
Assuming the lambda doesn't do naughty things to have db parameter escape the scope, the withDBConnection function will take care of handling the whole connection lifecycle.
znkr•1d ago
motorest•1d ago
RAII doesn't make resource freeing invisible. It makes it obvious, deterministic, and predictable. The runtime guarantees that, barring an abnormal program termination that's not handled well, your resource deallocation will take place. This behavior is not optional, or requires specifying ad-hoc blocks or sections.
motorest•1d ago
From those you listed, only Rust can be described as mainstream. Do you think that one out of a couple dozens refutes the statement that "most mainstream languages fail to support anything resembling RAII"?
> 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`?
Try-with-resources and the disposable pattern offer similar features but they still fall a bit short. Unlike RAII, they are not thread-safe, require specialized syntax and boilerplate code, and require manually specifying scopes.
But even if you consider try-with-resources and Disposable pattern as a perfect replacement of RAII, now point out how many mainstream languages support them. You have Java and JVM languages, you have C# and .NET languages, Python, and...
> I find those simpler than RAII.
Arguably this boils down to personal taste, but RAII actually ensures your resources will be released, and you can tell exactly when this will happen. Disposable patterns don't, and screwing up a using with statement is all it takes to get your application to leak resources and fail silently.
tialaramex•1d ago
In terms of languages you'd actually deploy today Rust is better both at this narrow feature and more broadly.
znkr•1d ago
motorest•1d ago
I don't think that's even conceptually the same. The point of RAII is that resource deallocation is ensured, deterministic, and built into the happy path. Once you start to throw errors and relying on those to manage resources, you're actually dealing the consequences of not having RAII.
oytis•1d ago
Or 'goto error8;' in C. Still RAII is much more convenient, especially for cases where you allocate a lot of interdependent resources at different time points. It keeps deallocation logic close to allocation logic (unlike, say, defer), makes sure deallocation always happens in the reverse order of allocations and doesn't force you to create a nested scope each time you allocate a resource
pjmlp•1d ago
pjmlp•1d ago
oytis•1d ago
bluGill•1d ago
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.
flohofwoe•1d ago
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.
oytis•1d ago
djmips•1d ago
sesm•1d ago
C++ was practical some decades ago (hardware-friendly variant of OOP for GUI), but it failed as a library language and the domain where it's practical on modern hardware is much smaller. I will not say anything about Rust.
delta_p_delta_x•1d ago
This is very inaccurate. Essentially every high-performance library, user-mode driver, desktop application, and more is written in nothing but C++. Give me any library you can think of, and I assure you it is written in C++ (or maybe C, but this is masochism on the part of the developers). Even libraries for other languages like numpy, pandas, pytorch, etc are written in C++.
flohofwoe•1d ago
C is the better choice when interoperability with other languages is needed (technically: a C API, the implementation language doesn't matter - but if a C++ implementation is wrapped in C API as an afterthought the result is usually a shitty C API). Personally I switched to C from C++ for writing libraries ca 2017 and don't regret that decision one bit.
Also, many C++ coders only have a foggy idea how convenient working in modern C can be, because their idea of C is usually the 'common C/C++ subset' that exists in C++, and this is stuck deep in the early 90s (for instance the designated-init feature in C++20 is a mere shadow of what C99 designated-init can do - to a point that the C++20 version is pretty much useless for real world usage).
delta_p_delta_x•1d ago
Here is a list of C++ features that C doesn't have, that are an immediate deal-breaker for me:
I can list more, but this is going to end up as a list of 'essentially every feature in C++ that isn't in C', which is the very reason for the former language to exist.flohofwoe•1d ago
oytis•1d ago
Totally agree about the domain - no one is writing enterprise applications in C++ these days, luckily. It still does have its domain though, where there is not much choice apart from C++ or Rust (or C if you are a dinosaur)
ninkendo•1d ago
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.
BoingBoomTschak•1d ago
tialaramex•1d ago
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.
BoingBoomTschak•19h ago
I'm not in love with them or the abomination known as C++, if that's what you're implying.
tialaramex•19h ago
How so? This definitely needs at least a citation of somebody who can explain in detail why this code is "impossible to rewrite". Who wrote the one we have now, Martians?
ogoffart•13h ago
What would you say is missing in Slint?
> probably not much harder than wrapping Qt, to be honest
There are already Qt bindings for rust (cxx-qt, qmetaobject-rs). What's missing from these?
green7ea•1d ago
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.