In recent projects I have mostly been following the Google C++ Style Guide https://google.github.io/styleguide/cppguide.html
The issue of exceptions is a tricky one. It’s close to impossible to retrofit C++ exception
use into a codebase not designed to allow exceptions, because all code which might be on the stack when an exception is flying around must control all resources with RAII (e.g. with 100% usage of std::shared_ptr/unique_ptr, and with file-descriptors and other OS objects carefully wrapped in RAII classes.
There’s a second wrinkle, which is that code called during exception-handling (roughly, anything in a destructor or possibly called from a destructor) must not itself allow an exception to escape. So destructors have to be extra careful about catching any possible exceptions.
I have recommendations about the use of C++'s various smart pointers and move semantics:
std::move should only be used inside the implementation of a container class.
If you’re using STL container classes rather than baking your own, then it’s just a
nice optimization that someone else has done.
Any expensive-to-copy classes should have move-constructors, so that the STL
optimization can work on them.
Use raw Foo* pointers only in performance-critical low-level code: hope that your
design allows this to be encapsulated in a small number of files adding up to a small
fraction of your codebase.
Use std::shared_ptr and std::shared_ptr for most of your code and most
of your objects. The speed and space overhead is low, and you’ll hardly ever have a
performance issue due to unnecessary copying of containers, or a hard-to-debug
For data structures with possible cycles of pointers, you’ll have to write a little extra
code to traverse the data structure and break the pointer-references to get it correctly
destroyed. But that situation doesn’t occur very often in compiler-like code, and it’s
not a lot of code to figure it out.
One huge advantage of this approach is that it keeps roughly the same semantics as
the implicit object references in just about every modern language - especially Java,
Scala, Python: assigning an object means copying the reference, not copying the
contents; objects no longer referenced are deallocated.
Use a consistent naming convention (and C++ “auto”) to keep this from becoming
excessively verbose, e.g.
class Foo is the object itself typedef std::shared_ptr<Foo> FooPtr; typedef std::shared_ptr<const Foo> FooCPtr;
or, since you’re mostly going to using the references rather than the contents:
class FooData is the object itself typedef std::shared_ptr<FooData> Foo; typedef std::shared_ptr<const FooData> FooC;
or whatever you like, as long as everyone can agree and we don’t end up with
gigabytes of verbose “std::shared_ptr” obfuscating the codebase.
Look up the non-obvious std::enable_shared_from_this. It allows you to
produce new std::shared_ptr’s from inside a T::whatever method, and have them
work correctly with other shared_ptr’s referring to the same object.
The multiple-references-to-one-object becomes hugely important in multi-threaded
code, which is also where it’s hardest to manage object lifetimes. std::shared_ptr is
the only sane way to handle that: if you don’t use it directly, you’ll end up reinventing it.
It also becomes important to distinguish between possibly-shared immutable data, and
not-(yet-)shared mutable data - using both std::shared_ptr and
std::shared_ptr can help, though it would be much nicer to have a notion of
deep-immutable objects, which would disallow mutation of a container and anything
reachable from the container.
Update: some benchmarking of std::shared_ptr here: