Apache Mesos - C++ Style Guide

Mesos C++ Style Guide

The Mesos codebase follows the Google C++ Style Guide with some notable differences, as described below.

Note that the clang-format tool can be helpful to ensure that some of the mechanical style rules are obeyed. Commits should be checked with the script support/mesos-style.py for high-level conformance, or with support/mesos-tidy.sh for conformance to low-level expectations.

Scoping

Namespaces

Naming

Variable Names

Try(State _state, T* _t = nullptr, const std::string& _message = "")
  : state(_state), t(_t), message(_message) {}
// You can pass-by-value in ProtobufProcess::install() handlers.
void Slave::statusUpdate(StatusUpdate update, const UPID& pid)
{
  ...
  update.mutable_status()->set_source(
      pid == UPID() ? TaskStatus::SOURCE_SLAVE : TaskStatus::SOURCE_EXECUTOR);
  ...
}

Constant Names

Function Names

Class Names

Strings

Comments

// Use `SchedulerDriver::acceptOffers()` to send several offer
// operations. This makes use of the `RESERVE()` and `UNRESERVE()`
// helpers, which take a `Resources` object as input and produce
// appropriate offer operations. Note that we are unreserving the
// resources contained in `dynamicallyReserved1`.
driver.acceptOffers({offer.id()},
    {UNRESERVE(dynamicallyReserved1),
     RESERVE(dynamicallyReserved2),
     RESERVE(dynamicallyReserved3)},
    filters);

Breaks

Indentation

Class Format

Templates

Function Definition/Invocation

// Preferred.
Owned<MasterDetector> detector = master.get()->createDetector();

// Don't use.
Owned<MasterDetector> detector = master.get().get().createDetector();
// 1: OK.
allocator->resourcesRecovered(frameworkId, agentId, resources, filters);

// 2: Don't use.
allocator->resourcesRecovered(frameworkId, agentId,
                              resources, filters);

// 3: Don't use in this case due to "jaggedness".
allocator->resourcesRecovered(frameworkId,
                              agentId,
                              resources,
                              filters);

// 3: In this case, 3 is OK.
foobar(someArgument,
       someOtherArgument,
       theLastArgument);

// 4: OK.
allocator->resourcesRecovered(
    frameworkId,
    agentId,
    resources,
    filters);

// 5: OK.
allocator->resourcesRecovered(
    frameworkId, agentId, resources, filters);

Continuation

Try<Duration> failoverTimeout =
  Duration::create(FrameworkInfo().failover_timeout());

Empty Lines

Try<very_very_long_type> long_name =
  ::protobuf::parse<very_very_long_type>(
      request);

for (int i = 0; i < very_very_long_expression();
     i++) {
  // No empty line here for control constructs.
}

Capture by Reference

We disallow capturing temporaries by reference. See MESOS-2629 for the rationale.

Future<Nothing> f() { return Nothing(); }
Future<bool> g() { return false; }

struct T
{
  T(const char* data) : data(data) {}
  const T& member() const { return *this; }
  const char* data;
};

// 1: Don't use.
const Future<Nothing>& future = f();

// 1: Instead use.
const Future<Nothing> future = f();

// 2: Don't use.
const Future<Nothing>& future = Future<Nothing>(Nothing());

// 2: Instead use.
const Future<Nothing> future = Future<Nothing>(Nothing());

// 3: Don't use.
const Future<bool>& future = f().then(lambda::bind(g));

// 3: Instead use.
const Future<bool> future = f().then(lambda::bind(g));

// 4: Don't use (since the T that got constructed is a temporary!).
const T& t = T("Hello").member();

// 4: Preferred alias pattern (see below).
const T t("Hello");
const T& t_ = t.member();

// 4: Can also use.
const T t = T("Hello").member();

We allow capturing non-temporaries by constant reference when the intent is to alias.

The goal is to make code more concise and improve readability. Use this if an expression referencing a field by const is:

hashmap<string, hashset<int>> index;

// 1: Ok.
const hashset<int>& values = index[2];

// 2: Ok.
for (auto iterator = index.begin(); iterator != index.end(); ++iterator) {
  const hashset<int>& values = iterator->second;
}

// 3: Ok.
foreachpair (const string& key, const hashset<int>& values, index) {}
foreachvalue (const hashset<int>& values, index) {}
foreachkey (const string& key, index) {}

// 4: Avoid aliases in most circumstances as they can be dangerous.
//    This is an example of a dangling alias!
vector<string> strings{"hello"};

string& s = strings[0];

strings.erase(strings.begin());

s += "world"; // THIS IS A DANGLING REFERENCE!

File Headers

Order of includes

In addition to the ordering rules from the Google style guide, Mesos related headers are separated into sections. Newline to separate each section. Mesos related headers in include directories are partitioned by their subfolders, sorted alphabetically, and included using brackets. Header in src directories are included afterwards, using the same rules but with quotes instead of brackets.

Example for src/common/foo.cpp:

#include "common/foo.hpp"

#include <stdint.h>

#include <string>
#include <vector>

#include <boost/circular_buffer.hpp>

#include <mesos/mesos.hpp>
#include <mesos/type_utils.hpp>

#include <mesos/module/authenticator.hpp>

#include <mesos/scheduler/scheduler.hpp>

#include <process/http.hpp>
#include <process/protobuf.hpp>

#include <stout/foreach.hpp>
#include <stout/hashmap.hpp>

#include "common/build.hpp"
#include "common/protobuf_utils.hpp"

#include "master/flags.hpp"

C++11

We support C++11 and require GCC 4.8+ or Clang 3.5+ compilers. The whitelist of supported C++11 features is:

// 1: OK.
const auto i = values.find(keys.front());
// Compare with
const typename map::iterator i = values.find(keys.front());

// 2: OK.
auto names = shared_ptr<list<string>>(new list<string>());
// Compare with
shared_ptr<list<string>> names = shared_ptr<list<string>>(new list<string>());

// 3: Don't use.
auto authorizer = LocalAuthorizer::create(acls);
// Compare with
Try<Owned<LocalAuthorizer>> authorizer = LocalAuthorizer::create();
// 1: OK.
[]() { ...; };

// 2: Don't use.
[] () { ...; };
// 1: OK.
[]() mutable { ...; };
// 1: OK.
[]() { return true; };
[]() -> bool { return ambiguous(); };
// 1: OK.
auto lambda = []() { ...; };
// 1: OK.
auto lambda = []() {
  ...;
};

// 2: OK.
auto lambda = []() { ...; };
instance.method([]() {
  ...;
});
// 1: OK.
instance
  .method([]() {
    ...;
  })
  .then([]() { ...; })
  .then([]() {
    ...;
  });

// 2: OK (when no chaining, compare to 1).
instance.method([]() {
  ...;
});

// 3: OK (if no 'instance.method').
function([]() {
  ...;
})
.then([]() { ...; })
.then([]() {
  ...;
});

// 3: OK (but prefer 1).
instance.method([]() {
  ...;
})
.then([]() { ...; })
.then([]() {
  ...;
});
// 1: OK.
function([&capture1, &capture2, &capture3](
    const T1& p1, const T2& p2, const T3& p3) {
  ...;
});

function(
    [&capture1, &capture2, &capture3](
        const T1& p1, const T2& p2, const T3& p3) {
  ...;
});

auto lambda = [&capture1, &capture2, &capture3](
    const T1& p1, const T2& p2, const T3& p3) {
  ...;
};

auto lambda =
  [&capture1, &capture2, &capture3](
      const T1& p1, const T2& p2, const T3& p3) {
  ...;
};

// 2: OK (when capture list is longer than 80 characters).
function([
    &capture1,
    &capture2,
    &capture3,
    &capture4](
        const T1& p1, const T2& p2) {
  ...;
});

auto lambda = [
    &capture1,
    &capture2,
    &capture3,
    &capture4](
        const T1& p1, const T2& p2) {
  ...;
};

// 3: OK (but prefer 2).
function([
    &capture1,
    &capture2,
    &capture3,
    &capture4](const T1& p1, const T2& t2) {
  ...;
});

auto lambda = [
    &capture1,
    &capture2,
    &capture3,
    &capture4](const T1& p1, const T2& p2) {
  ...;
};

// 3: Don't use.
function([&capture1,
          &capture2,
          &capture3,
          &capture4](const T1& p1, const T2& p2) {
  ...;
});

auto lambda = [&capture1,
               &capture2,
               &capture3,
               &capture4](const T1& p1, const T2& p2) {
  ...;
  };

// 4: Don't use.
function([&capture1,
           &capture2,
           &capture3,
           &capture4](
    const T1& p1, const T2& p2, const T3& p3) {
  ...;
});

auto lambda = [&capture1,
               &capture2,
               &capture3,
               &capture4](
    const T1& p1, const T2& p2, const T3& p3) {
  ...;
};

// 5: Don't use.
function([&capture1,
          &capture2,
          &capture3,
          &capture4](
    const T1& p1,
    const T2& p2,
    const T3& p3) {
  ...;
  });

auto lambda = [&capture1,
               &capture2,
               &capture3,
               &capture4](
    const T1& p1,
    const T2& p2,
    const T3& p3) {
  ...;
};

// 6: OK (parameter list longer than 80 characters).
function([&capture1, &capture2, &capture3](
    const T1& p1,
    const T2& p2,
    const T3& p3,
    const T4& p4) {
  ...;
});

auto lambda = [&capture1, &capture2, &capture3](
    const T1& p1,
    const T2& p2,
    const T3& p3,
    const T4& p4) {
  ...;
};

// 7: OK (capture and parameter lists longer than 80 characters).
function([
    &capture1,
    &capture2,
    &capture3,
    &capture4](
        const T1& p1,
        const T2& p2,
        const T3& p3,
        const T4& p4) {
  ...;
});

auto lambda = [
    &capture1,
    &capture2,
    &capture3,
    &capture4](
        const T1& p1,
        const T2& p2,
        const T3& p3,
        const T4& p4) {
  ...;
};
  // OK
  constexpr char LITERAL[] = "value";

  // Not OK - not available at compile time for optimization and
  // definition required in a separate compilation module.
  const char LITERAL[];

  // Not OK - uncertain initialization order, cannot be used in other
  // constexpr statements.
  const string LITERAL("value");

constexpr functions are evaluated at compile time if all their arguments are constant expressions. Otherwise they default to initialization at runtime. However constexpr functions are limited in that they cannot perform dynamic casts, memory allocation or calls to non-constexpr functions. Prefer constexpr over const inline functions.

  constexpr size_t MIN = 200;
  constexpr size_t MAX = 1000;
  constexpr size_t SPAN() { return MAX-MIN; }
  int array[SPAN()];

Const expression constructors allow object initialization at compile time provided that all the constructor arguments are constexpr and the constructor body is empty, i.e. all initialization is performed in the initialization list. Classes which provide constexpr constructors should normally also provide constexpr copy constructors to allow the class to be used in the return value from a constexpr function.

  class C
  {
  public:
    constexpr C(int _i) : i(_i) {};
    constexpr C(const C& c) : i(c.i) {}
  private:
    const int i;
  };

C++11 does not provide constexpr string or constexpr containers in the STL and hence constexpr cannot be used for any class using stout’s Error() class.

When overriding a virtual member function, the override keyword should always be used. The Google C++ Style Guide supplies the rationale for this:

A function or destructor marked override or final that is not an override of a base class virtual function will not compile, and this helps catch common errors. The specifiers serve as documentation; if no specifier is present, the reader has to check all ancestors of the class in question to determine if the function or destructor is virtual or not.