Like quite a few developers of my generation I started programming on my Commodore 64. Initially BASIC, then 6502 assembly. I never got around to doing anything serious with it, but still. I eventually got a PC (briefly interrupted by an Amiga 600) and experimented with Turbo Pascal. However, in order to do anything cool I had to learn C.
I found a copy of Teach Yourself C in 21 days and got cracking. Sure enough, it worked out pretty well. I even eventually learnt just enough C++ to become really dangerous and design elaborate class structures in places where composition would've been much better. I digress, though. Sorry.
One thing I wasn't particularly good at, though, was keeping track of memory. Back then I was using Watcom C/C++ with DOS/4GW and it wouldn't complain about buffer overruns or use-after-free at all. Sure, it might eventually crash my application but it was usually in a place that had absolutely nothing to do with the code that caused the problem. Mostly it'd just silently corrupt some vtable and work on 50% of the runs of my app.
I got better over time, I guess. Initially by just Refcounting All The Things manually using tons of
Release() calls everywhere. This at least solved a few use-after-free issues but it didn't stop me from forgetting to call
Release() in the first place. Heck, if I didn't remember to
delete properly (or
deleteing when I should have
deleted....) why would I make sure I manage my refcounts?
Moving to Visual C++ helped somewhat. The VC CRT tries to be more helpful by crashing more often when you do stupid stuff and there was also a feature of the CRT that dumped a list of unfreed memory on program exit. Couple that with the D3D debug runtime I'd usually get everything to work.
It still sucked though.
Through a twist of fate I found people willing to hire me to write C++ for a living. This brought me in contact with the more...well, enterprisey side of C++. People who weren't afraid of Boost mostly. That's where I learned of smart pointers. When I asked a colleague there what these things with the funny angled brackets were and how they worked internally, I got the helpful answer "It's a pointer that's smart". Thanks.
It was my reference counting code! Except IT WAS COMPLETELY AUTOMATIC. What the what? This I gotta see! (etc.)
Turns out they were still a bitch to work with. The codebase there used smart pointers, sure, but it also used raw pointers interchangeably. They also enjoyed using references. Sometimes. Not always. Sometimes. Maybe. When I called into an API I had absolutely no idea what was going on under the hood since their system libraries were built by a different team with the code helpfully not-shared. Either they were too embarassed about it or they thought it would confuse the poor application developers.
As a junior dev it certainly did the latter. I had no idea where memory was coming and going. Later on in different companies I had more understanding and also more control over the codebase so smart pointers like
auto_ptr and the Boost
smart_ptr started to become workable. Even then it was hard to use though. Instead of smart pointers I learnt to use RAII properly and that was even more elegant. Until I tried to put a reference into a container or I had to transfer ownership of the reference from one class to another.
Because then there was C++11. Much like a Terrence Malick movie it took way too long to materialize and has such a wide scope it initially confuses the hell out of a lot of people. But that's okay. You don't need to understand all of it in order to use it.
For me the eye opener has been the combination of
std::move. In a hobby application of mine I have this concept of a
UiNode that holds a reference to a
Node. It's basically just a GUI class that holds some additional properties and delegates a few things to the
Node it owns. However sometimes I want to transfer the
Node to another
UiNode cannot have a
Node& node_ field. Normally I'd have used a shared pointer for this. Maybe passed a raw pointer into that other
UiNode to indicate it shouldn't delete it. Ownership has transferred, though.
std::move does. It allows me to move the
Node from one
UiNode to another
UiNode. The original owning
unique_ptr becomes a nullptr so I can't use-after-free (and if I do, my program crashes immediately) and the new owner holds the reference and will automatically delete the
Node when it goes out of scope.
Gee. Why are you telling me this?
No reason in particular, I guess. But it's just delightful to me (and I realize this sounds way more geeky than should be allowable in a public forum) that a seemingly unsolvable problem I came across nearly 20 years ago is finally fixed in the language that I did so much of my coding in. Why am I even asking myself these rhetorical questions, by the way? That just seems off somehow. I digress, though. Sorry.