When To Use Smart Pointers

You could very easily go crazy with smart pointers. It seems like the safe idea, until you realize that you could be tying up memory everywhere. Perhaps you handle them carefully to avoid that issue. A more pressing problem is the mess of code they require to use them, such as is the case if you template your smart pointer class. Even if you are ok with that, your compiler might not be. GCC, for example, has a bug that won’t let you nest templates within each other. Usually you have to create some sort of wrapper class around a template. That makes accessing the data even more tedious.

On top of this, it really isn’t necessary to be a control freak over pointers. Yeah, yeah, I can hear hundreds of paranoid programmers screaming I’m wrong (or so what-I’ve-read would suggest) (and admittedly, I have my own idea as to what it means to be a control freak), but there’s nothing wrong with using pointers openly if you handle them carefully and know where to use them as such.

To make sure I’m not doing something stupid, I came up with some rules for where to use raw pointers as opposed to smart pointers. This is by no means an exhaustive list, but it lays out the fundamentals.

1) Raw pointers may be used within simple classes. This means classes dedicated to a single purpose, such as a list. Here, care must be taken in how such simple classes are used. For me at least, simple classes with exposed pointers are generally limited in the locations they are used.

2) Class members {that are raw pointers} should be instantiated immediately or else be always checked for instantiation. One or the other – not both. If you’ve instantiated your pointers in the beginning, you don’t need to worry about them suddenly vanishing. But you must remember to delete them in the end.

2b) Immediately-instantiated members that are pointers should be re-instantiated after being deleted, and this re-instantiation must occur prior to the closing of the last class method (of that class instance) on the stack. That means a class instance needs to clean itself up. Whenever a class method is called, member data it deletes will be expected elsewhere. To avoid null pointer errors, it must be restored in some way. Otherwise, we’re forced to monitor it everywhere.

3) Raw pointer to members of a class should never be passed outside of the class without being const unless…

a) the class holding the data is a dedicated wrapper or…

b) the class receiving the data is a dedicated wrapper.

4) Every class with a raw pointer member must have a custom copy constructor to handle instantiation of that pointer and reference counting.

Most times, you should return only by address. However, I often make classes (usually structs, actually) that don’t always control their own instantiation and destruction. This is safe because the data goes to a dedicated wrapper that performs some handling on the data openly and, in a sense, acts as an extension of the class. That said, rules 2 and 3 also apply to the dedicated wrapper.

The primary issue with raw pointers is our inability to monitor their instantiation or lack thereof. Why else would we have smart pointers to keep track of references? By limiting the scope of their usage, it’s easier to track pointers.

One of the nice things about smart pointers is that they can directly control initialization of pointers when performing copy construction. It allows for writing a single copy constructor rather than a copy constructor for every class that uses a pointer. A copy constructor still has to be written for every class that has raw pointers as members, but the number of such classes can be limited, depending on the program logic. An important aspect of raw pointers as members is that – if they are not going to be copied (and consequently be unique to a class) – they still need to inherit from a base class that has reference counting. This allows pointer data to be shared without worrying about when it is deleted.

Concluding Thoughts

I’ve read in some places that any handling of raw pointers or passing them to functions is bad practice. I can’t recall where, but any clever chums in my audience probably know full well that there are number of good designs that don’t let this happen. Not to mention, you can do some bizarre things in C++ that make the alternative – passing by address – also act poorly.

Design dictates what you can do and what you must do. In the case of the Copper Virtual Machine, handling raw pointers was inevitable. However, the overall design is such that there should be no segmentation faults. Does that mean I didn’t encounter them in debugging? Of course not. What felt silly was finding a bug recently whose faulty code had a label above it (that obviously I wrote) that warned the code would segfault if I called it one too many times. Sure enough, I was abusing it. At least I had the foresight to see the problem. Next time I won’t ignore my own warnings.

Leave a comment