As humans, we are fallible and prone to making errors. Therefore, we should try to write our programs as safe as possible, and let tools like the compiler or Valgrind help us find problems before they lead to crashes in production.
It is clear that it is impossible to avoid pointers at all levels – they provide a mechanism for indirection that is invaluable for complex architectures – “all problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections” (Wheeler's law). However, raw pointers are too powerful for either us or the compiler to efficiently reason about.
- Pointers might be NULL.
- Pointers might reference invalid memory, freed memory, or unallocated memory. Hello, segfault!
- A pointer might point to a single value, or be array-like and point to a sequence of values. In the latter case, there might not be a clear length associated with the array-like pointer, which encourages buffer overruns.
- Pointers are difficult for the compiler to optimize.
- The lifetime of pointed-to values is difficult to manage: Who is responsible for deleting the value? What happens with other pointers referencing that memory?
C++ offers a number of mechanism that allows a programmer to more precisely indicate why they are using a pointer-like thing.
- References are guaranteed to be non-null, but can still have problems regarding lifetime. They are best used for function parameters, and effectively subsume the C patterns of pass-by-pointer (in contrast to the default pass-by-value) and out-parameters.
- Iterators are an array-like pointer that points to some element in a collection. Whenever you are tempted to increment a pointer, you actually wanted an Iterator. In practice, Iterators have no or only few problems regarding null pointers, lifetime, or buffer overruns.
- Smart Pointers solve the problems regarding lifetime, by explicitly specifying lifetime semantics for the value. It is impossible to write exception-safe code with raw pointers in some circumstances, but smart pointers make this trivial. In other respects, they retain the full flexibility of raw pointers – importantly, they can be NULL. Also note that
std::unique_ptr is a zero-cost abstraction, so whenever your pointer has a clear owner, using the smart pointer is a clear win in safety.
- STL types such as
std::string replace most uses of array-like pointers, and introduce proper lifetime management, proper copying, make buffer overruns less likely with correct use, and aren't even terribly inefficient. Unfortunately, they have an unsafe
operator, but you always have the option to use the bounds-checked
These solutions are not perfect, but they make it way more difficult to write dangerously wrong code. Unless you are optimizing for C compatibility or (in case of shared pointers, some usage scenarios of vectors) object size, there is no reason to forgo this safety. I have yet to see a good example where references, iterators, smart pointers, or collections were not a better, more self-documenting, and more correct solution to a problem than raw pointers.
Is it professional to use these safety features? Yes: it is professional to solve our problems correctly and efficiently. Debugging segfaults, buffer overruns, and memory leaks is not an efficient use of my time, so I'd rather avoid them from the start. With any half-decent IDE or editor (and C++11's
auto), this isn't even much more code to type.