# Code Review Guidelines This chapter describes some of the things you should be on the lookout when reviewing someone else's code. ## Pointers & References In cases when some code passes a pointer or reference, or if a code stores a pointer or reference you should take a careful look at the following. * Lifetime of the pointed to value (this includes both ownership and multithreaded access). * In case of a class, check validity of destructor and move/copy constructors. * Is the pointed to value mutated, if not it should be `const` (`const Type *` or `const Type &`). ## Allocators & Memory Resources With the introduction of polymorphic allocators (C++17 `` and our `utils/memory.hpp`) we get a more convenient type signatures for containers so as to keep the outward facing API nice. This convenience comes at a cost of less static checks on the type level due to type erasure. For example: std::pmr::vector first_vec(std::pmr::null_memory_resource()); std::pmr::vector second_vec(std::pmr::new_delete_resource()); second_vec = first_vec // What happens here? // Or with our implementation utils::MonotonicBufferResource monotonic_memory(1024); std::vector> first_vec(&monotonic_memory); std::vector> second_vec(utils::NewDeleteResource()); second_vec = first_vec // What happens here? In the above, both `first_vec` and `second_vec` have the same type, but have *different* allocators! This can lead to ambiguity when moving or copying elements between them. You need to watch out for the following. * Swapping can lead to undefined behaviour if the allocators are not equal. * Is the move construction done with the right allocator. * Is the move assignment done correctly, also it may throw an exception. * Is the copy construction done with the right allocator. * Is the copy assignment done correctly. ## Classes & Object Oriented Programming A common mistake is to use classes, inheritance and "OOP" when it's not needed. This sections shows examples of encountered cases. ### Classes without (Meaningful) Members class MyCoolClass { public: int BeCool(int a, int b) { return a + b; } void SaySomethingCool() { std::cout << "Hello!"; } }; The above class has no members (i.e. state) which affect the behaviour of methods. This class should need not exist, it can be easily replaced with a more modular (and shorter) design -- top level functions. int BeCool(int a, int b) { return a + b; } void SaySomethingCool() { std::cout << "Hello!"; } ### Classes with a Single Public Method clas MyAwesomeClass { public: MyAwesomeClass(int state) : state_(state) {} int GetAwesome() { return GetAwesomeImpl() + 1; } private: int state_; int GetAwesomeImpl() { return state_; } }; The above class has a `state_` and even a private method, but there's only one public method -- `GetAwesome`. You should check "Does the stored state have any meaningful influence on the public method?", similarly to the previous point. In the above case it doesn't, and the class should be replaced with a public function in `.hpp` while the private method should become a private function in `.cpp` (static or in anonymous namespace). // hpp int GetAwesome(int state); // cpp namespace { int GetAwesomeImpl(int state) { return state; } } int GetAwesome(int state) { return GetAwesomeImpl(state) + 1; } A counterexample is when the state is meaningful. class Counter { public: Counter(int state) : state_(state) {} int Get() { return state_++; } private: int state_; }; But even that could be replaced with a closure. auto MakeCounter(int state) { return [state]() mutable { return state++; }; } ### Private Methods Instead of private methods, top level functions should be preferred. The reasoning is completely explained in "Effective C++" Item 23 by Scott Meyers. In our codebase, even improvements to compilation times can be noticed if private methods in interface (`.hpp`) files are replaced with top level functions in implementation (`.cpp`) files. ### Inheritance The rule is simple -- if there are no virtual methods (but maybe destructor), then the class should be marked as `final` and never inherited. If there are virtual methods (i.e. class is meant to be inherited), make sure that either a public virtual destructor or a protected non-virtual destructor exist. See "Effective C++" Item 7 by Scott Meyers. Also take a look at "Effective C++" Items 32---39 by Scott Meyers. An example of how inheritance with no virtual methods is replaced with composition. class MyBase { public: virtual ~MyBase() {} void DoSomethingBase() { ... } }; class MyDerived final : public MyBase { public: void DoSomethingNew() { ... DoSomethingBase(); ... } }; With composition, the above becomes. class MyBase final { public: void DoSomethingBase() { ... } }; class MyDerived final { MyBase base_; public: void DoSomethingNew() { ... base_.DoSomethingBase(); ... } }; The composition approach is preferred as it encapsulates the fact that `MyBase` is used for the implementation and users only interact with the public interface of `MyDerived`. Additionally, you can easily replace `MyBase base_;` with a C++ PIMPL idiom (`std::unique_ptr base_;`) to make the code more modular with regards to compilation. More advanced C++ users will recognize that the encapsulation feature of the non-PIMPL composition can be replaced with private inheritance. class MyDerived final : private MyBase { public: void DoSomethingNew() { ... MyBase::DoSomethingBase(); ... } }; One of the common "counterexample" is the ability to store objects of different type in a container or pass them to a function. Unfortunately, this is not that good of a design. For example. class MyBase { ... // No virtual methods (but the destructor) }; class MyFirstClass final : public MyBase { ... }; class MySecondClass final : public MyBase { ... }; std::vector> first_and_second_classes; first_and_second_classes.push_back(std::make_unique()); first_and_second_classes.push_back(std::make_unique()); void FunctionOnFirstOrSecond(const MyBase &first_or_second, ...) { ... } With C++17, the containers for different types should be implemented with `std::variant`, and as before the functions can be templated. class MyFirstClass final { ... }; class MySecondClass final { ... }; std::vector> first_and_second_classes; // Notice no heap allocation, since we don't store a pointer first_and_second_classes.emplace_back(MyFirstClass()); first_and_second_classes.emplace_back(MySecondClass()); // You can also use `std::variant` here instead of template template void FunctionOnFirstOrSecond(const TFirstOrSecond &first_or_second, ...) { ... } Naturally, if the base class has meaningful virtual methods (i.e. other than destructor) it maybe is OK to use inheritance but also consider alternatives. See "Effective C++" Items 32---39 by Scott Meyers. ### Multiple Inheritance Multiple inheritance should not be used unless all base classes are pure interface classes. This decision is inherited from [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Inheritance). For example on how to design with and around multiple inheritance refer to "Effective C++" Item 40 by Scott Meyers. Naturally, if there *really* is no better design, then multiple inheritance is allowed. An example of this can be found in our codebase when inheriting Visitor classes (though even that could be replaced with `std::variant` for example). ## Code Format & Style If something doesn't conform to our code formatting and style, just refer the author to either [C++ Style](cpp-code-conventions.md) or [Other Code Conventions](other-code-conventions.md).