3.8.0
Freundlich's C++ toolkit
Coding style

Ownership

When parameters are passed to functions, especially constructors, use one of the following cases to indicate ownership:

  • When a function takes a copy, use T && for types that are expensive to copy. This forces callers to pass an rvalue reference and make it explicit if they need to provide a copy, e.g.
    void f(std::string &&);
    void g() { std::string test{"hello"}; f(std::string(test)); }
    In cases copies are not expensive, use T.
  • When a function only reads its parameter, use T const &.
  • When a function takes ownership of a class that cannot be copied, use fcppt::unique_ptr<T> &&.
  • When a function keeps a reference, use fcppt::reference<T>. In this case, it makes it clear to the caller that he has to keep the object alive.
  • When a function needs shared ownership, use fcppt::shared_ptr<T>. This case should be considered last.
  • Avoid regular pointers, especially nullptr.

Classes

Following are various points on how to design the interface of a class:

  • Avoid default constructors! Default constructors are usually a sign of bad design, e.g. some code wants to assign to an object but does not need its value. Instead of using a default constructor, simply pass the desired values, e.g. int i{0}; instead of int i{};. If you do not have a value for the object yet, do not create it in the first place!

    There are several types which replace standard types with the explicit intend not to be default-constructible, e.g. fcppt::function, fcppt::unique_ptr, fcppt::shared_ptr and fcppt::variant::object.

  • Give every class a (non default) constructor. This ties in with Ownership. Another point is that the initialization syntax for structs and classes allows for partial initialization, e.g.
    struct S { int i; int j; }; S x{42};
    However, most compilers issue a warning for this.
  • Make classes explicitly noncopyable using FCPPT_NONCOPYABLE in case their copy constructors / assignment operators that are generated by default should not be used, or in case they can not be generated because a member is not copyable. If a class should be movable but not copyable, explicitly provide a move constructor and move assignment operator. Most of the time, the implementation obtained by using = default can be used.
  • Do not use regular references as members. They make a class non assignable. Instead, use fcppt::reference.

Functions

Following are various points about functions:

  • Make functions total! In case they can return nothing, use fcppt::optional::object. In case they can return an error, use fcppt::either::object.
  • Avoid cases where certain combinations of parameters are invalid. These cases can usually be avoided by using fcppt::optional::object, fcppt::variant::object, and so on. If it is very difficult to avoid this, use FCPPT_ASSERT_PRE or throw an exception. Be sure to document this very carefully!
  • Do not use default parameters and do not use function overloading. It is better to provide a function with a different name or to change the typing of parameters, e.g. by using fcppt::optional::object.
  • Avoid functions that get multiple parameters of the same type. Use fcppt::strong_typedef to disambiguate which parameter is used for which purpose. For example, instead of void f(std::string const &name, std::string const &email); use In some cases, merging multiple parameters into one can also help, e.g. instead of void f(int x, int y);, use void f(fcppt::math::vector::static_<int,2>);.
  • Do not use return parameters! In case you need to return multiple objects, create a struct or class that encapsulates this.

Loops

Avoid explicit loops if possible. There are many cases in which an algorithm can be used instead. For example, instead of

int result{0};
for (int i{1}; i < n; ++i)
{
result += i;
}

you can use fcppt::algorithm::fold

fcppt::make_int_range(1, n), 0, [](int i, int result) { return i + result; })

Algorithms can only be used with ranges, not with iterators. In case you need to encapsulate iterators, use fcppt::iterator::range. There are also special ranges like fcppt::int_range or fcppt::enum_::range. Some algorithms also accept a metal::list as a range in case you need to iterate over types.

Initialization

Initialize all objects and make them const if possible, especially do not use default constructors.

Provide functions for cases in which initialization can not be expressed directly. Consider a function that initializes an array of arbitrary size:

template <typename T, std::size_t N>

Say you have a function T from_index(std::size_t) that creates an array element given an index. You can then initialize the array using

return fcppt::array::init<fcppt::array::object<T, N>>(
[](std::size_t i) { return from_index<T>(i); });

In case you really need uninitialized objects, create a constructor that takes fcppt::no_init.