8. Definitions
8.1 Type Names
8.1.1 Do not use multiple levels of pointer indirection
In C++, at most one level of pointer indirection combined with references is sufficient to express any algorithm or API. Instead of using multidimensional arrays, an array of containers or nested containers should be used. Code reliant on more than one level of pointer indirection will be less readable and more difficult to maintain.
#include <cstdint> #include <vector> void foo (int32_t const * const * const pp); // @@- Non-Compliant -@@ void foo (int32_t const * const & rp); // @@+ Compliant +@@ void foo (int32_t const (& ra) [10]); // @@+ Compliant +@@ void foo (std::vector <int32_t> const & rv); // @@+ Compliant +@@
Exception
Use of argv in the main function is allowed.
For example:
// main1 .cpp // Compliant: argv not used int main (); // main2 .cpp // Compliant: 2 levels of pointer indirection in argv int main (int argc , char * argv []); // main3 .cpp // Compliant: 2 levels of pointer indirection in argv int main (int argc , char * * argv );
References
- JSF AV C++ Rev C – 169
- JSF AV C++ Rev C – 170
- MISRA C++:2008 – 5-0-19
8.2 Meaning of Declarators
8.2.1 Make parameter names absent or identical in all declarations
Although the C++ standard does not mandate that parameter names match in all declarations of a function (e.g. a declaration in a header file and the definition in the main source file), it is good practice to follow this principle.
#include <cstdint> void read (int32_t * buffer, int32_t * size); void read (int32_t * size, int32_t * buffer) // @@- Non-Compliant -@@ { } class B { public: virtual void foo (int32_t in) = 0; }; class C : public B { public: void foo (int32_t) override // @@+ Compliant +@@ { } };
References
- HIC++ v3.3 – 11.3
8.2.2 Do not declare functions with an excessive number of parameters
A function defined with a long list of parameters often indicates poor design and is difficult to read and maintain. The recommended maximum number of function parameters is six.
#include <cstdint> #include <vector> // @@- Non-Compliant: 7 parameters -@@ void foo (int32_t mode , int32_t const * src , int32_t src_size , int32_t * dest , int32_t dest_size , bool padding , bool compress); // @@+ Compliant +@@ void foo (int32_t flags , std::vector <int32_t> const & src , std::vector <int32_t> & dest);
References
- HIC++ v3.3 – 4.3
8.2.3 Pass small objects with a trivial copy constructor by value
Because passing by const reference involves an indirection, it will be less efficient than passing by value for a small object with a trivial copy constructor.
#include <cstdint> class C { public: C (C const &) = default; // trivial copy constructor private: int32_t m_i; int32_t m_j; }; void foo (C v) // @@+ Compliant +@@ { } class D { public: D (D const &); // non-trivial (user defined) copy constructor private: int32_t m_i; int32_t m_j; }; void foo (D v) // @@- Non-Compliant -@@ { }
References
- Sutter Guru of the Week (GOTW) – 91
- HIC++ v3.3 – 11.4
- HIC++ v3.3 – 11.5
8.2.4 Do not pass std::unique_ptr by const reference
An object of type std::unique_ptr should be passed as a non-const reference, or by value. Passing by non-const reference signifies that the parameter is an in/out parameter. Passing by value signifies that the parameter is a sink (i.e. takes ownership and does not return it). A const reference std::unique_ptr parameter provides no benefits and restricts the potential callers of the function.
#include <cstdint> #include <memory> void foo (std::unique_ptr & p_in_out); // @@+ Compliant +@@ void foo (std::unique_ptr p_sink); // @@+ Compliant +@@ void foo (std::unique_ptr const & p_impl_detail); // @@- Non-Compliant -@@
References
- Sutter Guru of the Week (GOTW) – 91
8.3 Function Definitions
8.3.1 Do not write functions with an excessive McCabe Cyclomatic Complexity
The McCabe Cyclomatic Complexity is calculated as the number of decision branches within a function plus 1. Complex functions are hard to maintain and test effectively. It is recommended that the value of this metric does not exceed 10.
#include <cstdint> void foo (int32_t a, bool b, bool c) { if (a > 0) // 1 { if (b) // 2 { } for (int32_t i (0); i < a; ++i) // 3 { } if (c) // 4 { } } else if (0 == a) // 5 { if (b) // 6 { } if (c) // 7 { } } else { if (c) // 8 { } for (int32_t i (-a - 1); i >= 0; --i) // 9 { } if (b) // 10 { } } // @@- Non-Compliant: STCYC = #decisions + 1 = 11 -@@ }
References
- HIC++ v3.3 – 4.1
8.3.2 Do not write functions with a high static program path count
Static program path count is the number of non-cyclic execution paths in a function. Functions with a high number of paths through them are difficult to test, maintain and comprehend. The static program path count of a function should not exceed 200.
bool foo (); void bar () { if (foo ()) // 2 paths { } if (foo ()) // 4 paths { } if (foo ()) // 8 paths { } if (foo ()) // 16 paths { } if (foo ()) // 32 paths { } if (foo ()) // 64 paths { } if (foo ()) // 128 paths { } if (foo ()) // @@- Non-Compliant: 256 paths -@@ { } }
References
- HIC++ v3.3 – 4.2
8.3.3 Do not use default arguments
Use of default arguments can make code maintenance and refactoring more difficult. Overloaded forwarding functions can be used instead without having to change existing function calls.
#include <cstdint> void foo (int32_t i, int32_t j = 0); // @@- Non-Compliant -@@ // @@+ Compliant +@@ void bar (int32_t i, int32_t j); inline void bar (int32_t i) { bar (i, 0); }
References
- Make default arguments the same or absent when overriding a virtual function – 9.1.2
8.4 Initializers
8.4.1 Do not access an invalid object or an object with indeterminate value
A significant component of program correctness is that the program behavior should be deterministic. That is, given the same input and conditions the program will produce the same set of results. If a program does not have deterministic behavior, then this may indicate that the source code is reliant on unspecified or undefined behavior. Such behaviors may arise from use of:
- Variables not yet initialized.
- Memory (or pointers to memory) that has been freed.
- Moved from objects.
#include <cstdint> #include <iostream> class A { public: A(); // ... }; std::ostream operator<<(std::ostream &, A const &); int main () { int32_t i; A a; std::cout << i << std::endl; // @@- Non-Compliant: 'i' has indeterminate value -@@ std::cout << a << std::endl; // @@+ Compliant: Initialized by constructor call +@@ }
Note: For the purposes of this rule, after the call to std::move has been evaluated the moved from argument is considered to have an indeterminate value.
#include <cstdint> #include <vector> int main () { std::vector<int32_t> v1; std::vector<int32_t> v2; std::vector<int32_t> v3 (std::move (v1)); std::vector<int32_t> v4 (std::move (v2)); v1.empty (); // @@- Non-Compliant: 'v1' considered to have indeterminate value -@@ v2 = v4; // @@+ Compliant: New value assigned to 'v2' +@@ v2.empty (); // @@+ before it is accessed ' +@@ }
References
- Postpone variable definitions as long as possible - 6.4.1
- HIC++ v3.3 - 8.4.3
- C++11 - 8.5/11
8.4.2 Ensure that a braced aggregate initializer matches the layout of the aggregate object
If an array or a struct is non-zero initialized, initializers should be provided for all members, with an initializer list for each aggregate (sub)object enclosed in braces. This will make it clear what value each member is initialized with.
#include <cstdint> struct S { int32_t i; int32_t j; int32_t k; }; struct T { struct S s; int32_t a[5]; }; void foo () { S s1 = {0, 1}; // @@- Non-Compliant: one member is not explicitly initialized -@@ S s2 = {0, 1, 2}; // @@+ Compliant +@@ T t1 = {0, 1, 2, 3, 4, 5, 6, 7}; // @@- Non-Compliant -@@ T t2 = {0, 1, 2, {3, 4, 5, 6, 7}}; // @@- Non-Compliant -@@ T t3 = {{0, 1, 2}, {3, 4, 5, 6, 7}}; // @@+ Compliant +@@ }
References
- JSF AV C++ Rev C – 144
- MISRA C++:2008 – 8-5-2