7. Declarations
7.1 Specifiers
7.1.1 Declare each identifier on a separate line in a separate declaration
Declaring each variable or typedef on a separate line makes it easier to find the declaration of a particular identifier. Determining the type of a particular identifier can become confusing for multiple declarations on the same line.
#include <cstdint> // @@- Non-Compliant: what is the type of 'v' -@@ extern int32_t const * p, v; // @@- Non-Compliant: what type is 'Value' aliased to -@@ typedef int32_t* Pointer, Value;
Exception
For loop initialization statement is exempt from this rule, as in this context the rule conflicts with Rule 6.4.1: ”Postpone variable definitions as long as possible”, which takes precedence.
For example:
# include <vector> # include <cstdint> void foo (std :: vector const & v) { for ( auto iter (v. begin ()) , end (v.end ()) // Compliant ; iter != end ; ++ iter ) { // ... }
References
- HIC++ v3.3 – 8.4.2
- HIC++ v3.3 – 8.4.7
- HIC++ v3.3 – 13.3
- Meyers Notes – Reference Binding Rules
7.1.2 Use const whenever possible
This allows specification of semantic constraint which a compiler can enforce. It explicitly communicates to other programmers that value should remain invariant. For example, specify whether a pointer itself is const, the data it points to is const, both, or neither.
struct S { char* p1; // @@+ Compliant: non-const pointer to non-const data +@@ const char* p2; // @@+ Compliant: non-const pointer to const data +@@ char* const p3; // @@+ Compliant: const pointer to non-const data +@@ const char* const p4; // @@+ Compliant: const pointer to const data +@@ }; void foo (const char * const p); // @@+ Compliant +@@ void bar (S & s) // @@- Non-Compliant: parameter could be const qualified -@@ { foo (s.p1); foo (s.p2); foo (s.p3); foo (s.p4); }
Exception
By-value return types are exempt from this rule. These should not be const as doing so will inhibit move semantics.
struct A { }; const int f1 (); // Non-Compliant const A f2 () ; // Non-Compliant A f3 (); // Compliant
References
- HIC++ v3.3 – 8.4.11
- Going Native 2013 – Slide 24
7.1.3 Do not place type specifiers before non-type specifiers in a declaration
The C++ standard allows any order of specifiers in a declaration. However, to improve readability if a non-type specifier (typedef, friend, constexpr, register, static, extern, thread_local, mutable, inline, virtual, explicit) appears in a declaration, it should be placed leftmost in the declaration.
typedef int int32_t; // @@+ Compliant +@@ int typedef int32_e; // @@- Non-Compliant -@@ class C { public: virtual inline void f1 (); // @@+ Compliant +@@ inline virtual void f2 (); // @@+ Compliant +@@ void inline virtual f3 (); // @@- Non-Compliant -@@ private: int32_t mutable _i; // @@- Non-Compliant -@@ };
7.1.4 Place CV-qualifiers on the right hand side of the type they apply to
The const or volatile qualifiers can appear either to the right or left of the type they apply to. When the unqualified portion of the type is a typedef name (declared in a previous typedef declaration), placing the CV-qualifiers on the left hand side, may result in confusion over what part of the type the qualification applies to.
#include <cstdint> typedef int32_t * PINT; void foo (const PINT p1 // @@- Non-Compliant: the type is not const int32_t * -@@ , PINT const p2); // @@+ Compliant: the type is int32_t * const +@@
For consistency, it is recommended that this rule is applied to all declarations.
#include <cstdint> void bar (int32_t const & in); // @@+ Compliant +@@
7.1.5 Do not inline large functions
The definition of an inline function needs to be available in every translation unit that uses it. This in turn requires that the definitions of inline functions and types used in the function definition must also be visible. The inline keyword is just a hint, and compilers in general will only inline a function body if it can be determined that performance will be improved as a result. As the compiler is unlikely to inline functions that have a large number of statements and expressions, inlining such functions provides no performance benefit but will result in increased dependencies between translation units. Given an approximate cost of 1 for every expression and statement, the recommended maximum cost for a function is 32.
#include <cstdint> namespace NS { class C { public: C (int32_t) { m_i = (m_i + m_i + m_i + m_i + m_i + m_i + m_i); m_i = (m_i + m_i + m_i + m_i + m_i + m_i + m_i); m_i = (m_i + m_i + m_i + m_i + m_i + m_i + m_i); } int32_t foo () { m_i = (m_i + m_i + m_i + m_i + m_i + m_i + m_i); m_i = (m_i + m_i + m_i + m_i + m_i + m_i + m_i); } private: int m_i; }; // @@- Non-Compliant: Cost greater than 32 -@@ inline int32_t foo (int32_t i) { C c (i); return c.foo (); } }
References
- HIC++ v3.3 – 11.8
- HIC++ v3.3 – 6.4
- HIC++ v3.3 – 13.3
- Meyers Notes – Reference Binding Rules
7.1.6 Use class types or typedefs to abstract scalar quantities and standard integer types
Using class types to represent scalar quantities exploits compiler enforcement of type safety. If this is not possible, typedefs should be used to aid readability of code.
class Length; class Time; class Velocity; class Acceleration; // @@+ Compliant +@@ const Velocity operator / (Length, Time); const Velocity operator * (Acceleration, Time); const Velocity operator * (Time, Acceleration);
Plain char type should not be used to define a typedef name, unless the type is intended for parameterizing the code for narrow and wide character types. In other cases, an explicit signed char or unsigned char type should be used in a typedef as appropriate.
typedef char BYTE; BYTE foo (BYTE v) { return 2 * v; // @@- Non-Compliant: conversion from char to integer type -@@ }
To enhance portability, instead of using the standard integer types (signed char, short, int, long, long long, and the unsigned counterparts), size specific types should be defined in a project-wide header file, so that the definition can be updated to match a particular platform (16, 32 or 64bit). Where available, intN_t and uintN_t types (e.g. int8_t) defined in the cstdint header file should be used for this purpose.
// @@+ Compliant: x64 platform +@@ typedef signed char int8_t; typedef short int16_t; typedef int int32_t; typedef long long int64_t;
Where the auto type specifier is used in a declaration, and the initializer is a constant expression, the declaration should not be allowed to resolve to a standard integer type. The type should be fixed by casting the initializer to a size specific type.
#include <cstdint> void foo () { auto a (0); // @@- Non-Compliant: int -@@ auto b (0L); // @@- Non-Compliant: long -@@ auto c (0U); // @@- Non-Compliant: unsigned int -@@ auto d (static_cast (0)); // @@+ Compliant +@@ int32_t e (0); // @@+ Compliant +@@ }
Exception
The C++ language standard places type requirements on certain constructs. In such cases, it is better to use required type explicitly rather than the typedef equivalent which would reduce the portability of the code.
The following constructs are therefore exceptions to this rule:
- int main()
- T operator++(int)
- T operator–(int)
For example:
class A { public : A operator ++( int ); // Compliant A operator --( int ); // Compliant }; int main () // Compliant { }
References
- HIC++ v3.3 – 8.4.5
- HIC++ v3.3 – 8.4.6
7.1.7 Use a trailing return type in preference to type disambiguation using typename
When using a trailing return type, lookup for the function return type starts from the same scope as the function declarator. In many cases, this will remove the need to specify a fully qualified return type along with the typename keyword.
template <typename T> class A { typedef T TYPE; TYPE f1(TYPE); TYPE f2(TYPE); }; template <typename T> typename A<T>::TYPE A<T>::f1 (TYPE) // @@- Non-Compliant -@@ { } template <typename T> auto A<T>::f2 (TYPE) -> TYPE // @@+ Compliant +@@ { }
References
- Sutter Guru of the Week (GOTW) – 93
7.1.8 Use auto id = expr when declaring a variable to have the same type as its initializer function call
When declaring a variable that is initialized with a function call, the type is being specified twice. Initially on the return of the function and then in the type of the declaration.
#include <cstdint> int32_t foo(); // Type 'int32_t' specified here int main () { int32_t i = foo(); // @@- Non-Compliant: Type 'int32_t' again specified here. -@@ }
Using auto and implicitly deducing the type of the initializer will ensure that a future change to the declaration of foo will not result in the addition of unexpected implicit conversions.
#include <cstdint> int32_t foo(); // Type 'int32_t' specified here int main () { auto i = foo(); // @@+ Compliant: 'i' deduced to have 'int32_t'. +@@ }
References
- Sutter Guru of the Week (GOTW) – 93
- Use const container calls when result is immediately converted to a const iterator – 17.4.1
7.1.9 Do not explicitly specify the return type of a lambda
Allowing the return type of a lambda to be implicitly deduced reduces the danger of unexpected implicit conversions, as well as simplifying future maintenance, where changes to types used in the lambda would otherwise result in the need to change the return type.
#include <vector> #include <algorithm> #include <cstdint> bool f(std::vector<int32_t> const & v1, std::vector<int32_t> & v2) { v2.reserve(v1.size()); std::transform (v1.cbegin () , v1.cend () , v2.begin() , [](std::vector<int32_t>::value_type i) -> int32_t // @@- Non-Compliant -@@ { return i + 10; } ); std::transform (v1.cbegin () , v1.cend () , v2.begin() , [](std::vector<int32_t>::value_type i) // @@+ Compliant +@@ { return i + 10; } ); }
In the above example, if the element type of v1 and v2 changes, then the return type on the first lambda must also be changed, however, the second lambda will operate correctly without any update.
References
- Sutter Guru of the Week (GOTW) – 93
7.1.10 Use static_assert for assertions involving compile time constants
A static_assert will generate a compile error if its expression is not true. The earlier that a problem can be diagnosed the better, with the earliest time possible being as the code is written.
#include <cassert> template <typename T, int N> bool f(T i) { // @@- Non-Compliant -@@ assert((sizeof(T)*8) == N && "Expect that the size of the type matches the value specified by N"); // @@+ Compliant +@@ static_assert((sizeof(T)*8) == N , "Expect that the size of the type matches the value specified by N"); }
7.2 Enumeration Declarations
7.2.1 Use an explicit enumeration base and ensure that it is large enough to store all enumerators
The underlying type of an unscoped enumeration is implementation defined, with the only restriction being that the type must be able to represent the enumeration values. An explicit enumeration base should always be specified with a type that will accommodate both the smallest and the largest enumerator. A scoped enum will implicitly have an underlying type of int, however, the requirement to specify the underlying type still applies.
#include <cstdint> enum E1 // @@- Non-Compliant -@@ { E1_0, E1_1, E1_2 }; enum E2 : int8_t // @@+ Compliant +@@ { E2_0, E2_1, E2_2 }; enum class E3 // @@- Non-Compliant -@@ { E1_0, E1_1, E1_2 }; enum class E4 : int32_t // @@+ Compliant +@@ { E2_0, E2_1, E2_2 };
Exception
An enumeration declared in an extern "C" block (i.e. one intended to be used with C) does not require an explicit underlying type.
For example:
extern "C" Copyright © Programming Research 73 High Integrity C++ Version 4.0 7 Declarations { enum E3 // Compliant by Exception { E3_0 , E3_1 , E3_2 }; }
7.2.2 Initialize none, the first only or all enumerators in an enumeration
It is error-prone to initialize explicitly only some enumerators in an enumeration, and to rely on the compiler to initialize the remaining ones. For example, during maintenance it may be possible to introduce implicitly initialized enumerators with the same value as an existing one initialized explicitly.
#include <cstdint> enum E : int32_t { RED , ORANGE = 2 // @@- Non-Compliant -@@ , YELLOW };
Exception
When an enumeration is used to define the size and to index an array, it is acceptable and recommended to define three additional enumerators after all other enumerators, to represent the first and the last elements, and the size of the array.
For example:
# include <cstdint> // Compliant enum Team : int32_t { Anna , Bob , Joe , John , Sandra , Tim , Team_First = Anna , Team_Last = Tim , Team_Size }; int32_t performance [ Team_Size ];
References
- JSF AV C++ Rev C – 145
- MISRA C++:2008 – 8-5-3
7.3 Namespaces
7.3.1 Do not use using directives
Namespaces are an important tool in separating identifiers and in making interfaces explicit. A using directive, i.e. using namespace, allows any name to be searched for in the namespace specified by the using directive. A using declaration, on the other hand, brings in a single name from the namespace, as if it was declared in the scope containing the using declaration.
#include <iostream> using namespace std; // @@- Non-Compliant -@@ using std::cout; // @@+ Compliant +@@ #include <string> // unqualified name string is looked up in namespace std // even though the using directive precedes the // declaration in the translation unit string foo ();
As pointed out in the example above a using directive will affect all subsequent lookup in the translation unit. For this reason using directives are particularly problematic if used in header files, or occur in the main source file above a #include pre-processor directive. This may lead to ambiguity (compilation error) or maintenance and reuse problems.
References
- HIC++ v3.3 – 8.2.3
7.4 Linkage Specifications
7.4.1 Ensure that any objects, functions or types to be used from a single translation unit are defined in an unnamed namespace in the main source file
Declaring an entity in an unnamed namespace limits its visibility to the current translation unit only. This helps reduce the risk of name clashes and conflicts with declarations in other translation units. It is preferred to use unnamed namespaces rather than the static keyword to declare such entities.
static void foo (); // @@- Non-Compliant -@@ namespace { void bar (); // @@+ Compliant +@@ }
References
- HIC++ v3.3 – 8.3.1
7.4.2 Ensure that an inline function, a function template, or a type used from multiple translation units is defined in a single header file
An inline function, a function template or a user defined type that is intended for use in multiple translation units should be defined in a single header file, so that the definition will be processed in exactly the same way (the same sequence of tokens) in each translation unit. This will ensure that the one definition rule is adhered to, avoiding undefined behavior, as well as improving the maintainability of the code.
#include <cstdint> // @@- Non-Compliant: definition of user defined type in the main source file -@@ struct S { int32_t i; int32_t j; }; #include <cstdint> // @@- Non-Compliant: ODR violation, undefined behavior -@@ struct S { int16_t i; int32_t j; };
References
- HIC++ v3.3 – 8.1.2
- HIC++ v3.3 – 8.1.3
7.4.3 Ensure that an object or a function used from multiple translation units is declared in a single header file
An object or function with external linkage should be declared in a single header file in the project. This will ensure that the type seen for an entity in each translation unit is the same thereby avoiding undefined behavior.
// file1.cpp // @@- Non-Compliant: first declaration of extern function in the main source file -@@ extern void foo (); // file2.cpp // @@- Non-Compliant: no prior declaration in a header file -@@ extern void foo () { // ... }
References
- HIC++ v3.3 – 8.1.1
7.5 The asm Declaration
7.5.1 Do not use the asm declaration
Use of inline assembly should be avoided since it restricts the portability of the code.
#include <cstdint> int32_t foo () { int32_t result; asm (""); // @@- Non-Complaint -@@ return result; }
References
- HIC++ v3.3 – 13.5