C++ - google style guide etc


#1

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:

  1. 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.

  2. Any expensive-to-copy classes should have move-constructors, so that the STL
    optimization can work on them.

  3. 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.

  4. 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
    dangling-pointer bug.

    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.

  5. 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.

  6. 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.

  7. 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: