Skip to content

Instantly share code, notes, and snippets.

@sasq64
Last active April 25, 2019 11:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sasq64/0cf7ba4cc656d2fb1ff38d4022365fd8 to your computer and use it in GitHub Desktop.
Save sasq64/0cf7ba4cc656d2fb1ff38d4022365fd8 to your computer and use it in GitHub Desktop.

C++ Developer Guide

Please note; these rules are not random; nor are they my personal preferences. They represent -- for the most part -- an emerging 'de facto' standard for how modern C++ code is developed.

Basics

C++14 "modern" style

  • Don't use naked new/delete
  • Prefer passing and returning by value
  • Use unique_ptr unless you need sharing
  • Return unique_ptr from factory functions, they can be turned into shared_ptr.
  • Don't use indexed for loop when foreach is enough.
  • Look to algorithms instead of complex foreach loops

Class Design

Try to avoid '-er' type classes (Manager, Controller, Handler etc). It's often hard to determine what their purpose is.

Sometimes you need a class whos only purpose is to handle a set of (logical) child classes. But they should normally be short and do only that. If actual business logic starts to sneak in to your manager class, it's either time to rename it or (better) move that logic somewhere else.

Project layout

We try to follow the pitchfork propsal

We use a single, top level CMakeLists.txt

We also have a boot strapping Makefile so you can just type make after cloning.

File structure

Put the interface in the header and the implementation in the cpp file. The cpp file should include the headers as its first line, even if it is otherwise empty.

Use #pragma once instead of ifdef guards; it is supported by all compilers and is less error prone (and probably faster).

Sort your includes;

  • The corresponding class h-file first.
  • Then other local include files.
  • Then 3rd party headers.
  • Last std library includes.

Try to keep dependencies out of inlude files.

Source code style

  • LLVM as base
  • Spaces for tabs
  • 80 column limit
namespace ecp {

class StateMachine
{
public:
    void doSomething(std::string const& name)
    {
        if (name.empty()) {
            return;
        }
    }
};

} // namespace ecp

Naming

Camel case, Upper case for classes and structs, lower case for methods and variables.

Namespaces should be lower case and kept short.

Use UPPERCASE only for macros, not for enums and contants, to avoid collisions with existing macros.

If you need to name your member variables differently, use a trailing underscore; ie myMemberVariable_.

Priority; where to spend most time thinking about good names

  • Classes
  • Functions
  • Member variables
  • Function arguments
  • Local variables
  • For loop / small clause variables

ie Think really hard about what a class should be called so it conveys as clearly as possible what it does, and change the name if the responsibility of the class changes.

As a special case, function arguments should of course have good names, but you should not need to see them to understand the function.

The usage of a function should perferably be clear by it's name and the types of its arguments.

Error Handling

Distinguish between errors and nonerrors. A failure is an error if and only if it violates a function's ability to meet its callees' preconditions, to establish its own postconditions, or to reestablish an invariant it shares responsibility for maintaining. Everything else is not an error.

Herb Sutter

Check pre conditions at compile time if possible (static_assert). For errors, we use exceptions. We can catch and rethrow in jni layer.

Tools

  • clang-format everything -- don't spend mental energy on formatting source code.
  • Address sanitizer and valgrind to find memory problems and leaks.
  • clang-tidy for liniting.
  • cmake-format to keep the CMakeLists.txt clean.

Testing

Aim for test driven design.

We use catch2 and put all our unit tests under tests/

Since you don't have tests for your tests;

Test code should be understandable and correct by inspection.

Also because test code is a good starting place to understand the rest of the code.

Avoid special test utility methods, or test base classes. Try to write top-down, understandable code.

Documentation

Try to document API level methods and classes with descriptive text, using
//! comments like these

We can then use Doxygen to generate documentation.

Motivation / Contention

80 Columns

Boost uses 80 columns. The clang standard library uses 80 columns. Most lines are shorter, so a longer linewidth wastes more screen real estate.

With clang-format, you don't need to do line breaking manually, it will format the code good enough to be readable.

It is univerally understood that long lines are hard to read. This is why newspapers format things into columns. For code this can break up statements too much and at some point it will make it harder to read. As far as I know there has been no studies made to figure out where the "breaking point" lies, but I think the concencus in the community is that it is below 80 characters.

Don't let the historical significance of the number fool you into thinking this is just a legacy rule. It's just a nice, even number that seems to be a good trade off between too long lines and two much wrapping.

East const

I prefer fn(std::string const& arg) instead of fn(const std::string& arg). There has been a lot of debate about this, but the concesus is basically that the first (east const) is more logical, but the second is the standard.

Either one is OK.

Curly braces

Some C++ programmers (mainly those used to C#) prefer to put a new line before every curly brace.

Java code never puts new lines before braces.

The de facto C++ standard is a mix, and not really a better alternative than either Java or C#, but since there is no standard that is universal, we may as well go with the de facto C++ standard.

Naming

The C++ community is moving away from CamelCase towards lower snake_case for everything. This guide still recommends the older more universal standard that is similar to Java and C#. I think this still reflects what most developers are used to, but it would be more correct to switch to snake case. We can still start types with upper case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment