Which Languages Are Best for Multilingual Projects

Below are 15 countries/regions—selected based on population size, economic output, and international influence—together with their language codes (shortcodes) and brief rationales, intended as a reference for multilingual translation:

Country / RegionShortcodeBrief Rationale
United Statesen-USEnglish is the global lingua franca; the U.S. has the world’s largest GDP (population: 333 million) and is a core market for international business and technology.
Chinazh-CNMost populous country (1.41 billion); 2nd-largest GDP; Chinese is a UN official language; Chinese market consumption potential is enormous.
Japanja-JPJapanese is the official language of the world’s 5th-largest economy; leading in technology and manufacturing; population: 125 million with strong purchasing power.
Germanyde-DECore of the Eurozone economy; largest GDP in Europe; German wields significant influence within the EU; population: 83.2 million with a robust industrial base.
Francefr-FRFrench is a UN official language; France has the 7th-largest GDP globally; population: 67.81 million; widely used in Africa and international organizations.
Indiahi-INHindi is one of India’s official languages; India’s population (1.4 billion) is the world’s 2nd-largest; 6th-largest GDP and among the fastest-growing major economies.
Spaines-ESSpanish has the 2nd-largest number of native speakers worldwide (548 million); Spain’s GDP is 4th in Europe, and Spanish is common throughout Latin America.
Brazilpt-BRPortuguese is the native language of Brazil (population: 214 million); Brazil is South America’s largest economy with the 9th-largest GDP globally.
South Koreako-KRKorean corresponds to South Korea (population: 51.74 million); 10th-largest GDP globally; powerful in technology and cultural industries such as K-pop.
Russiaru-RURussian is a UN official language; population: 146 million; GDP ranks 11th globally; widely spoken in Central Asia and Eastern Europe.
Italyit-ITItaly’s GDP is 3rd in Europe; population: 59.06 million; strong in tourism and luxury goods; Italian is an important EU language.
Indonesiaid-IDIndonesian is the official language of the world’s largest archipelagic nation (population: 276 million) and the largest GDP in Southeast Asia, presenting a high market potential.
Turkeytr-TRTurkish is spoken by 85 million people; Turkey’s strategic position bridging Europe and Asia; GDP ranks 19th globally and exerts cultural influence in the Middle East and Central Asia.
Netherlandsnl-NLDutch is spoken in the Netherlands (population: 17.5 million); GDP ranks 17th globally; leading in trade and shipping; although English penetration is high, the local market still requires the native language.
United Arab Emiratesar-AEArabic is central to the Middle East; the UAE is a Gulf economic hub (population: 9.5 million, 88 % expatriates) with well-developed oil and finance sectors, radiating influence across the Arab world.

Notes: Language codes follow the ISO 639-1 (language) + ISO 3166-1 (country) standards, facilitating adaptation to localization tools.
Priority has been given to countries with populations over 100 million, GDPs in the world’s top 20, and those with notable regional influence, balancing international applicability and market value.
For particular domains (e.g., the Latin American market can add es-MX (Mexico), Southeast Asia can add vi-VN (Vietnam)), the list can be further refined as needed.

Third-party Library Pitfalls

  • Third-party library pitfalls

Today we talked about a vulnerability in a recently released third-party logging library that can be exploited with minimal effort to execute remote commands. A logging library and remote command execution seem completely unrelated, yet over-engineered third-party libraries are everywhere.

The more code I read, the more I feel that a lot of open-source code is of very poor quality—regardless of how many k-stars it has. Stars represent needs, not engineering skill.

The advantage of open source is that more people contribute, allowing features to grow quickly, bugs to get fixed, and code to be reviewed. But skill levels vary wildly.

Without strong commit constraints, code quality is hard to guarantee.

The more code you add, the larger the attack surface.

Although reinventing the wheel is usually bad, product requirements are like a stroller wheel: a plastic wheel that never breaks. Attaching an airplane tire to it just adds attack surface and maintenance costs. So if all you need is a stroller wheel, don’t over-engineer.

Maintenance is expensive. Third-party libraries require dedicated processes and people to maintain them. Huawei once forked a test framework, and when we upgraded the compiler the test cases failed. Upgrading the test framework clashed with the compiler upgrade, so we burned ridiculous time making further invasive changes. As one of the people involved, I deeply felt the pain of forking third-party libraries. If the modifications were feature-worthy they could be upstreamed, but tailoring them to our own needs through intrusive customization makes future maintenance nearly impossible.

Huawei created a whole series of processes for third-party libraries—one could say the friction was enormous.

The bar is set very high: adding a library requires review by a level-18 expert and approval from a level-20 director. Only longtime, well-known libraries even have a chance.

All third-party libraries live under a thirdparty folder. A full build compares them byte-for-byte with the upstream repo; any invasive change is strictly forbidden.

Dedicated tooling tracks every library’s version, managed by outsourced staff. If a developer wants to upgrade a library, they must submit a formal request—and the director has to sign off.

Getting a director to handle something like that is hard. When a process is deliberately convoluted, its real message is “please don’t do this.”

Approach third-party libraries with skepticism—trust code written by your own team.

Design Specification Template

  • Design Specification Template

Detailed Design of XXX System / Sub-system

System NameXXX System
AuthorXXX
Submission Date2021-06-30

Revision History

Revised VersionChange DescriptionDate of ChangeAuthor
v1.0XXXXXXX2021-06-30XXX

Technical Review Comments

No.ReviewerReview Comment (Pass/Fail/Pending, comments allowed)Review Time
1XXXPass2022.1.1

Background

Glossary

  • SIP: Session Initiation Protocol
  • RTP: Real-time Transport Protocol

Design Objectives

Functional Requirements

Non-Functional Requirements (mandatory)

Environment

System Constraints

Estimated Data Scale (mandatory)

Existing Solutions

Design Ideas & Trade-offs

Assumptions & Dependencies / Relationships with Other Systems

System Design

Overview

Architecture Diagram & Explanation

System Flow & Explanation (optional)

Interfaces with External Systems

Global Data-Structure Descriptions

Brief Description of Module XXX1

Functionality of Module XXX1

Interfaces with Other Modules

Brief Description of Module XXX2

Functionality of Module XXX2

Interfaces with Other Modules

Threat Modeling

Upgrade Impact (mandatory)

Risk Assessment & Impact on Other Systems (optional)

Known or Foreseeable Risks

Potential Impact with Other Systems/Modules

Innovation Points (optional)

Attachments & References

Command Line Syntax Conventions

  • Command line syntax conventions

References

e.g.

NotationDescription
Text without brackets or bracesItems you must type as shown.
<Text inside angle brackets>Placeholder for which you must supply a value.
[Text inside square brackets]Optional items.
{Text inside braces}Set of required items. You must choose one.
Vertical bar ( | )Separator for mutually exclusive items. You must choose one.
Ellipsis ()Items that can be repeated and used multiple times.

Meanings of brackets in man pages

  • Meanings of brackets in man pages

Meanings of brackets in man pages

In command-line help, different types of brackets generally carry the following meanings:

  1. Angle brackets <>:
    • Angle brackets denote required arguments—values you must provide when running the command. They’re typically used to express the core syntax and parameters of a command.
    • Example: command <filename> means you must supply a filename as a required argument, e.g., command file.txt.
  2. Square brackets []:
    • Square brackets indicate optional arguments—values you may or may not provide when running the command. They’re commonly used to mark optional parameters and options.
    • Example: command [option] means you can choose to provide an option, e.g., command -v or simply command.
  3. Curly braces {}:
    • Curly braces usually represent a set of choices, indicating that you must select one. These are also called “choice parameter groups.”
    • Example: command {option1 | option2 | option3} means you must pick one of the given options, e.g., command option2.
  4. Parentheses ():
    • Parentheses are generally used to group arguments, clarifying structure and precedence in a command’s syntax.
    • Example: command (option1 | option2) filename means you must choose either option1 or option2 and supply a filename as an argument, e.g., command option1 file.txt.

These bracket conventions are intended to help users understand command syntax and parameter choices so they can use command-line tools correctly. When reading man pages or help text, paying close attention to the meaning and purpose of each bracket type is crucial—it prevents incorrect commands and achieves the desired results.

Huawei C++ Coding Standards

  • Huawei C++ Coding Standards

C++ Language Coding Standards

Purpose

Rules are not perfect; by prohibiting features useful in specific situations, they may impact code implementation. However, the purpose of establishing rules is “to benefit the majority of developers.” If, during team collaboration, a rule is deemed unenforceable, we hope to improve it together.

Before referencing this standard, it is assumed that you already possess the corresponding basic C++ language capabilities; do not rely on this document to learn the C++ language.

  1. Understand the C++ language ISO standard;
  2. Be familiar with basic C++ language features, including those related to C++ 03/11/14/17;
  3. Understand the C++ standard library;

General Principles

Code must, while ensuring functional correctness, meet the feature requirements of readability, maintainability, safety, reliability, testability, efficiency, and portability.

Key Focus Areas

  1. Define the C++ coding style, such as naming, formatting, etc.
  2. C++ modular design—how to design header files, classes, interfaces, and functions.
  3. Best practices for C++ language features, such as constants, type casting, resource management, templates, etc.
  4. Modern C++ best practices, including conventions in C++11/14/17 that can improve maintainability and reliability.
  5. This standard is primarily applicable to C++17.

Conventions

Rule: A convention that must be followed during programming (must).
Recommendation: A convention that should be followed during programming (should).

This standard applies to common C++ standards; when no specific standard version is noted, it applies to all versions (C++03/11/14/17).

Exceptions

Regardless of ‘Rule’ or ‘Recommendation’, you must understand the reasons behind each item and strive to follow them.
However, there may be exceptions to some rules and recommendations.

If it does not violate the general principles and, after thorough consideration, there are sufficient reasons, it is acceptable to deviate from the conventions in the specification.
Exceptions break code consistency—please avoid them. Exceptions to a ‘Rule’ should be extremely rare.

In the following situations, stylistic consistency should take priority:
When modifying external open-source code or third-party code, follow the existing code style to maintain uniformity.

2 Naming

General Naming

CamelCase
Mixed case letters with words connected. Words are separated by capitalizing the first letter of each word.
Depending on whether the first letter after concatenation is capitalized, it is further divided into UpperCamelCase and lowerCamelCase.

TypeNaming Style
Type definitions such as classes, structs, enums, and unions, as well as scope namesUpperCamelCase
Functions (including global, scoped, and member functions)UpperCamelCase
Global variables (including global and namespace-scoped variables, class static variables), local variables, function parameters, member variables of classes, structs, and unionslowerCamelCase
Macros, constants (const), enum values, goto labelsALL CAPS, underscore separator

Notes:
The constant in the above table refers to variables at global scope, namespace scope, or static member scope that are basic data types, enums, or string types qualified with const or constexpr; arrays and other types of variables are excluded.
The variable in the above table refers to all variables other than the constant definition above, which should all use the lowerCamelCase style.

File Naming

Rule 2.2.1 C++ implementation files end with .cpp, header files end with .h

We recommend using .h as the header suffix so header files can be directly compatible with both C and C++.
We recommend using .cpp as the implementation file suffix to clearly distinguish C++ code from C code.

Some other suffixes used in the industry:

  • Header files: .hh, .hpp, .hxx
  • cpp files: .cc, .cxx, .c

If your project team already uses a specific suffix, you can continue using it, but please maintain stylistic consistency.
For this document, we default to .h and .cpp as suffixes.

Rule 2.2.2 C++ file names correspond to the class name

C++ header and cpp file names should correspond to the class names in lowercase with underscores.

If a class is named DatabaseConnection, then the corresponding filenames should be:

  • database_connection.h
  • database_connection.cpp

File naming for structs, namespaces, enums, etc., follows a similar pattern.

Function Naming

Function names use the UpperCamelCase style, usually a verb or verb-object structure.

class List {
public:
    void AddElement(const Element& element);
    Element GetElement(const unsigned int index) const;
    bool IsEmpty() const;
};

namespace Utils {
    void DeleteUser();
}

Type Naming

Type names use the UpperCamelCase style.
All type names—classes, structs, unions, type aliases (typedef), and enums—use the same convention, e.g.:

// classes, structs and unions
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...
union Packet { ...

// typedefs
typedef std::map<std::string, UrlTableProperties*> PropertiesMap;

// enums
enum UrlTableErrors { ...

For namespaces, UpperCamelCase is recommended:

// namespace
namespace OsUtils {
 
namespace FileUtils {
     
}
 
}

Recommendation 2.4.1 Avoid misusing typedef or #define to alias basic types

Do not redefine basic data types with typedef/#define unless there is a clear necessity.
Prefer using basic types from the <cstdint> header:

Signed TypeUnsigned TypeDescription
int8_tuint8_tExactly 8-bit signed/unsigned integer
int16_tuint16_tExactly 16-bit signed/unsigned integer
int32_tuint32_tExactly 32-bit signed/unsigned integer
int64_tuint64_tExactly 64-bit signed/unsigned integer
intptr_tuintptr_tSigned/unsigned integer to hold a pointer

Variable Naming

General variables use lowerCamelCase, covering global variables, function parameters, local variables, and member variables.

std::string tableName;  // Good: recommended
std::string tablename;  // Bad: prohibited
std::string path;       // Good: single-word lowercase per lowerCamelCase

Rule 2.5.1 Global variables must have a ‘g_’ prefix; static variables need no special prefix

Global variables should be used sparingly; adding the prefix visually reminds developers to use them carefully.

  • Static global variables share the same naming as global variables.
  • Function-local static variables share normal local variable naming.
  • Class static member variables share normal member variable naming.
int g_activeConnectCount;

void Func()
{
    static int packetCount = 0; 
    ...
}

Rule 2.5.2 Class member variables are named in lowerCamelCase followed by a trailing underscore

class Foo {
private:
    std::string fileName_;   // trailing _ suffix, similar to K&R style
};

For struct/union member variables, use lowerCamelCase without suffix, consistent with local variables.

Macro, Constant, and Enum Naming

Macros and enum values use ALL CAPS, underscore-connected format.
Global-scope or named/anonymous-namespace const constants, as well as class static member constants, use ALL CAPS, underscore-connected; function-local const constants and ordinary const member variables use lowerCamelCase.

#define MAX(a, b)   (((a) < (b)) ? (b) : (a)) // macro naming example only—macro not recommended for such a feature

enum TintColor {    // enum type name in UpperCamelCase, values in ALL CAPS, underscore-connected
    RED,
    DARK_RED,
    GREEN,
    LIGHT_GREEN
};

int Func(...)
{
    const unsigned int bufferSize = 100;    // local constant
    char *p = new char[bufferSize];
    ...
}

namespace Utils {
    const unsigned int DEFAULT_FILE_SIZE_KB = 200;        // global constant
}

3 Format

Line Length

Rule 3.1.1 Do not exceed 120 characters per line

We recommend keeping each line under 120 characters. If 120 characters are exceeded, choose a reasonable wrapping method.

Exceptions:

  • Lines containing commands or URLs in comments may remain on one line for copy/paste / grep convenience, even above 120 chars.
  • #include statements with long paths may exceed 120 chars but should be avoided when possible.
  • Preprocessor error messages may span one line for readability, even above 120 chars.
#ifndef XXX_YYY_ZZZ
#error Header aaaa/bbbb/cccc/abc.h must only be included after xxxx/yyyy/zzzz/xyz.h, because xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#endif

Indentation

Rule 3.2.1 Use spaces for indentation—4 spaces per level

Only use spaces for indentation (4 spaces per level). Do not use tab characters.
Almost all modern IDEs can be configured to automatically expand tabs to 4 spaces—please configure yours accordingly.

Braces

Rule 3.3.1 Use K&R indentation style

K&R Style
For functions (excluding lambda expressions), place the left brace on a new line at the beginning of the line, alone; for other cases, the left brace should follow statements and stay at the end of the line.
Right braces are always on their own line, unless additional parts of the same statement follow—e.g., while in do-while, else/else-if of an if-statement, comma, or semicolon.

Examples:

struct MyType {     // brace follows statement with one preceding space
    ...
};

int Foo(int a)
{                   // function left brace on a new line, alone
    if (...) {
        ...
    } else {
        ...
    }
}

Reason for recommending this style:

  • Code is more compact.
  • Continuation improves reading rhythm compared to new-line placement.
  • Aligns with later language conventions and industry mainstream practice.
  • Modern IDEs provide alignment aids so the end-of-line brace does not hinder scoping comprehension.

For empty function bodies, the braces may be placed on the same line:

class MyClass {
public:
    MyClass() : value_(0) {}
   
private:
    int value_;
};

Function Declarations and Definitions

Rule 3.4.1 Both return type and function name must be on the same line; break and align parameters reasonably when line limit is exceeded

When declaring or defining functions, the return type and function name must appear on the same line; if permitted by the column limit, place parameters on the same line as well—otherwise break parameters onto the next line with proper alignment.
The left parenthesis always stays on the same line as the function name; placing it on its own line is prohibited. The right parenthesis always follows the last parameter.

Examples:

ReturnType FunctionName(ArgType paramName1, ArgType paramName2)   // Good: all on one line
{
    ...
}

ReturnType VeryVeryVeryLongFunctionName(ArgType paramName1,     // line limit exceeded, break
                                        ArgType paramName2,     // Good: align with previous
                                        ArgType paramName3)
{
    ...
}

ReturnType LongFunctionName(ArgType paramName1, ArgType paramName2, // line limit exceeded
    ArgType paramName3, ArgType paramName4, ArgType paramName5)    // Good: 4-space indent after break
{
    ...
}

ReturnType ReallyReallyReallyReallyLongFunctionName(            // will not fit first parameter, break immediately
    ArgType paramName1, ArgType paramName2, ArgType paramName3) // Good: 4-space indent after break
{
    ...
}

Function Calls

Rule 3.5.1 Keep function argument lists on one line. If line limit is exceeded, align parameters correctly when wrapping

Function calls should have their parameter list on one line—if exceeding the line length, break and align parameters accordingly.
Left parenthesis always follows the function name; right parenthesis always follows the last parameter.

Examples:

ReturnType result = FunctionName(paramName1, paramName2);   // Good: single line

ReturnType result = FunctionName(paramName1,
                                 paramName2,                // Good: aligned with param above
                                 paramName3);

ReturnType result = FunctionName(paramName1, paramName2,
    paramName3, paramName4, paramName5);                    // Good: 4-space indent on break

ReturnType result = VeryVeryVeryLongFunctionName(           // cannot fit first param, break immediately
    paramName1, paramName2, paramName3);                    // 4-space indent after break

If parameters are intrinsically related, grouping them for readability may take precedence over strict formatting.

// Good: each line represents a group of related parameters
int result = DealWithStructureLikeParams(left.x, left.y,     // group 1
                                         right.x, right.y);  // group 2

if Statements

Rule 3.6.1 if statements must use braces

We require braces for all if statements—even for single-line conditions.

Rationale:

  • Code logic is intuitive and readable.
  • Adding new code to an existing conditional is less error-prone.
  • Braces protect against macro misbehavior when using functional macros (in case the macro omitted braces).
if (objectIsNotExist) {         // Good: braces for single-line condition
    return CreateNewObject();
}

Rule 3.6.2 Prohibit writing if/else/else if on the same line

Branches in conditional statements must appear on separate lines.

Correct:

if (someConditions) {
    DoSomething();
    ...
} else {  // Good: else on new line
    ...
}

Incorrect:

if (someConditions) { ... } else { ... } // Bad: else same line as if

Loops

Rule 3.7.1 Loop statements must use braces

Similar to conditions, we require braces for all for/while loops—even if the body is empty or contains only one statement.

for (int i = 0; i < someRange; i++) {   // Good: braces used
    DoSomething();
}
while (condition) { }   // Good: empty body with braces
while (condition) {
    continue;           // Good: continue indicates empty logic, still braces
}

Bad examples:

for (int i = 0; i < someRange; i++)
    DoSomething();      // Bad: missing braces
while (condition);      // Bad: semicolon appears part of loop, confusing

switch Statements

Rule 3.8.1 Indent case/default within switch bodies one additional level

Indentation for switch statements:

switch (var) {
    case 0:             // Good: indented
        DoSomething1(); // Good: indented
        break;
    case 1: {           // Good: brace indentation if needed
        DoSomething2();
        break;
    }
    default:
        break;
}

Bad:

switch (var) {
case 0:                 // Bad: case not indented
    DoSomething();
    break;
default:                // Bad: default not indented
    break;
}

Expressions

Recommendation 3.9.1 Consistently break long expressions at operators, operators stranded at EOL

When an expression is too long for one line, break at an appropriate operator. Place the operator at the end-of-line, indicating ‘to be continued’.

Example:
// Assume first line exceeds limit

if ((currentValue > threshold) &&  // Good: logical operator at EOL
    someCondition) {
    DoSomething();
    ...
}

int result = reallyReallyLongVariableName1 +    // Good
             reallyReallyLongVariableName2;

After wrapping, either align appropriately or indent subsequent lines by 4 spaces.

int sum = longVariableName1 + longVariableName2 + longVariableName3 +
    longVariableName4 + longVariableName5 + longVariableName6;        // Good: 4-space indent

int sum = longVariableName1 + longVariableName2 + longVariableName3 +
          longVariableName4 + longVariableName5 + longVariableName6;  // Good: aligned

Variable Assignment

Rule 3.10.1 Multiple variable declarations and initializations are forbidden on the same line

One variable initialization per line improves readability and comprehension.

int maxCount = 10;
bool isCompleted = false;

Bad examples:

int maxCount = 10; bool isCompleted = false; // Bad: multiple initializations must span separate lines
int x, y = 0;  // Bad: multiple declarations must be on separate lines

int pointX;
int pointY;
...
pointX = 1; pointY = 2;  // Bad: multiple assignments placed on same line

Exception: for loop headers, if-with-initializer (C++17), structured binding statements (C++17), etc., may declare and initialize multiple variables; forcing separation would hinder scope correctness and clarity.

Initialization

Includes initialization for structs, unions, and arrays.

Rule 3.11.1 Indent initialization lists when wrapping; align elements properly

When breaking struct/array initializers, each continuation is indented 4 spaces. Choose break and alignment points for readability.

const int rank[] = {
    16, 16, 16, 16, 32, 32, 32, 32,
    64, 64, 64, 64, 32, 32, 32, 32
};

Pointers and References

Recommendation 3.12.1 Place the pointer star ‘*’ adjacent to the variable or type—never add spaces on both sides or omit both

Pointer naming: align ‘*’ to either left (type) or right (variable) but never leave/pad both sides.

int* p = nullptr;  // Good
int *p = nullptr;  // Good

int*p = nullptr;   // Bad
int * p = nullptr; // Bad

Exception when const is involved—’*’ cannot trail the variable, so avoid trailing the type:

const char * const VERSION = "V100";

Recommendation 3.12.2 Place the reference operator ‘&’ adjacent to the variable or type—never pad both sides nor omit both

Reference naming: & aligned either left (type) or right (variable); never pad both sides or omit spacing.

int i = 8;

int& p = i;     // Good
int &p = i;     // Good
int*& rp = pi;  // Good: reference to pointer; *& together after type
int *&rp = pi;  // Good: reference to pointer; *& together after variable
int* &rp = pi;  // Good: pointer followed by reference—* with type, & with variable

int & p = i;    // Bad
int&reeeenamespace= i;  // Bad—illustrates missing space or doubled up spacing issues

Preprocessor Directives

Rule 3.13.1 Place preprocessing ‘#’ at the start of the line; nested preprocessor statements may indent ‘#’ accordingly

Preprocessing directives’ ‘#’ must be placed at the beginning of the line—even if inside function bodies.

Rule 3.13.2 Avoid macros except where necessary

Macros ignore scope, type systems, and many rules and are prone to error. Prefer non-macro approaches, and if macros must be used, ensure unique macro names.
In C++, many macro use cases can be replaced:

  • Use const or enum for intuitive constants
  • Use namespaces to prevent name conflicts
  • Use inline functions to avoid call overhead
  • Use template functions for type abstraction

Macros may be used for include guards, conditional compilation, and logging where required.

Rule 3.13.3 Do not use macros to define constants

Macros are simple text substitution completed during pre-processing; typeless, unscoped, and unsafe. Debugging errors display the value, not the macro name.

Rule 3.13.4 Do not use function-like macros

Before defining a function-like macro, consider if a real function can be used. When alternatives exist, favor functions.
Disadvantages of function-like macros:

  • Lack type checking vs function calls
  • Macro arguments are not evaluated (behaves differently than function calls)
  • No scope encapsulation
  • Heavily cryptic syntax (macro operator # and eternal parentheses) harms readability
  • Extensions needed (e.g., GCC statement expressions) hurt portability
  • Compiler sees the macro after pre-processing; multi-line macro expansions collapse into one line, hard to debug or breakpoint
  • Repeated expansion of large macros increases code size

Functions do not suffer the above negatives, although worst-case cost is reduced performance via call overhead (or due to micro-architecture optimization hassles).
To mitigate, use inline. Inline functions:

  • Perform strict type checking
  • Evaluate each argument once
  • Expand in place with no call overhead
  • May optimize better than macros

For performance-critical production code, prefer inline functions over macros.

Exception:
logging macros may need to retain FILE/LINE of the call site.

Whitespace/Blank Lines

Rule 3.14.1 Horizontal spaces should emphasize keywords and key information, avoid excessive whitespace

Horizontal spaces should highlight keywords and key info; do not pad trailing spaces. General rules:

  • Add space after keywords: if, switch, case, do, while, for
  • Do not pad inside parentheses both-left-and-right
  • Maintain symmetry around braces
  • No space after unary operators (& * + - ~ !)
  • Add spaces around binary operators (= + - < > * / % | & ^ <= >= == !=)
  • Add spaces around ternary operators ( ? : )
  • No space between pre/post inc/dec (++, –) and variable
  • No space around struct member access (., ->)
  • No space before comma, but space after
  • No space between <> and type names as in templates or casts
  • No space around scope operator ::
  • Colon (:) spaced according to context when needed

Typical cases:

void Foo(int b) {  // Good: space before left brace

int i = 0;  // Good: spaces around =; no space before semicolon

int buf[BUF_SIZE] = {0};    // Good: no space after {

Function definition/call examples:

int result = Foo(arg1,arg2);
//                      ^    Bad: comma needs trailing space

int result = Foo( arg1, arg2 );
//               ^        ^     Bad: no space after ( before args; none before )

Pointer/address examples:

x = *p;     // Good: no space between * and p
p = &x;     // Good: no space between & and x
x = r.y;    // Good: no space around .
x = r->y;   // Good: no space around ->

Operators:

x = 0;   // Good: spaces around =
x = -5;  // Good: no space between - and 5
++x;     // Good: no space between ++ and x
x--;

if (x && !y)  // Good: spaces around &&, none between ! and y
v = w * x + y / z;  // Good: Binary operators are surrounded by spaces.
v = w * (x + z);    // Good: Expressions inside parentheses are not padded with spaces.

int a = (x < y) ? x : y;  // Good: Ternary operators require spaces before ? and :.

Loops and conditional statements:

if (condition) {  // Good: A space after if, none inside the parentheses
    ...
} else {           // Good: A space after else
    ...
}

while (condition) {}   // Good: Same rules as if

for (int i = 0; i < someRange; ++i) {  // Good: Space after for, after each semicolon
    ...
}

switch (condition) {  // Good: One space between switch and (
    case 0:     // Good: No space between case label and colon
        ...
        break;
    ...
    default:
        ...
        break;
}

Templates and casts

// Angle brackets (< and >) are never preceded by spaces, nor followed directly by spaces.
vector<string> x;
y = static_cast<char*>(x);

// One space between a type and * is acceptable; keep it consistent.
vector<char *> x;

Scope resolution operator

std::cout;    // Good: No spaces around ::

int MyClass::GetValue() const {}  // Good: No spaces around ::

Colons

// When spaces are required

// Good: Space before colon with class derivation
class Sub : public Base {

};

// Constructor initializer list needs spaces
MyClass::MyClass(int var) : someVar_(var)
{
    DoSomething();
}

// Bit field width also gets a space
struct XX {
    char a : 4;
    char b : 5;
    char c : 4;
};
// When spaces are NOT required

// Good: No space after public:, private:, etc.
class MyClass {
public:
    MyClass(int var);
private:
    int someVar_;
};

// No space after case or default in switch statements
switch (value)
{
    case 1:
        DoSomething();
        break;
    default:
        break;
}

Note: Configure your IDE to strip trailing whitespace.

Advice 3.14.1 Arrange blank lines to keep code compact

Avoid needless blank lines to display more code on screen and improve readability. Observe these guidelines:

  • Insert blank lines based on logical sections, not on automatic habits.
  • Do not use consecutive blank lines inside functions, type definitions, macros, or initializer lists.
  • Never use three or more consecutive blank lines.
  • Do not leave blank lines right after a brace that opens a block or right before the closing brace; the only exception is within namespace scopes.
int Foo()
{
    ...
}



int Bar()  // Bad: more than two consecutive blank lines.
{
    ...
}


if (...) {
        // Bad: blank line immediately after opening brace
    ...
        // Bad: blank line immediately before closing brace
}

int Foo(...)
{
        // Bad: blank line at start of function body
    ...
}

Classes

Rule 3.15.1 Order class access specifiers as public:, protected:, private: at the same indentation level as class

class MyClass : public BaseClass {
public:      // Note: no extra indentation
    MyClass();  // standard 4-space indent
    explicit MyClass(int var);
    ~MyClass() {}

    void SomeFunction();
    void SomeFunctionThatDoesNothing()
    {
    }

    void SetVar(int var) { someVar_ = var; }
    int GetVar() const { return someVar_; }

private:
    bool SomeInternalFunction();

    int someVar_;
    int someOtherVar_;
};

Within each section group similar declarations and order them roughly as follows: type aliases (typedef, using, nested structs / classes), constants, factory functions, constructors, assignment operators, destructor, other member functions, data members.

Rule 3.15.2 Constructor initializer lists should fit on one line or be indented four spaces and line-wrapped

// If everything fits on one line:
MyClass::MyClass(int var) : someVar_(var)
{
    DoSomething();
}

// Otherwise, place after the colon and indent four spaces
MyClass::MyClass(int var)
    : someVar_(var), someOtherVar_(var + 1)  // Good: space after comma
{
    DoSomething();
}

// If multiple lines are needed, align each initializer
MyClass::MyClass(int var)
    : someVar_(var),            // indent 4 spaces
      someOtherVar_(var + 1)
{
    DoSomething();
}

4 Comments

Prefer clear architecture and naming; only add comments when necessary to aid understanding.

Keep comments concise, accurate, and non-redundant.
Comments are as important as code.
When you change code, update all relevant comments—leaving old comments is destructive.

Use English for all comments.

Comment style

Both /* */ and // are acceptable.
Choose one consistent style for each comment category (file header, function header, inline, etc.).

Note: samples in this document frequently use trailing // merely for explanation—do not treat it as an endorsed style.

File header

Rule 3.1 File headers must contain a valid license notice

/*

  • Copyright (c) 2020 XXX
  • Licensed under the Apache License, Version 2.0 (the “License”);
  • you may not use this file except in compliance with the License.
  • You may obtain a copy of the License at
  • http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software
  • distributed under the License is distributed on an “AS IS” BASIS,
  • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  • See the License for the specific language governing permissions and
  • limitations under the License. */

Function header comments

Rule 4.3.1 Provide headers for public functions

Callers need to know behavior, parameter ranges, return values, side effects, ownership, etc.—and the function signature alone cannot express everything.

Rule 4.3.2 Never leave noisy or empty function headers

Not every function needs a comment.
Only add a header when the signature is insufficient.

Headers appear above the declaration/definition using one of the following styles: With //

// One-line function header
int Func1(void);

// Multi-line function header
// Second line
int Func2(void);

With /* */

/* Single-line function header */
int Func1(void);

/*
 * Alternative single-line style
 */
int Func2(void);

/*
 * Multi-line header
 * Second line
 */
int Func3(void);

Cover: purpose, return value, performance hints, usage notes, memory ownership, re-entrancy etc.

Example:

/*
 * Returns bytes actually written, -1 on failure.
 * Caller owns the buffer buf.
 */
int WriteString(const char *buf, int len);

Bad:

/*
 * Function name: WriteString
 * Purpose: write string
 * Parameters:
 * Return:
 */
int WriteString(const char *buf, int len);

Problems:‐ parameters/return empty, function name redundant, unclear ownership.

Inline code comments

Rule 4.4.1 Place comments directly above or to the right of the code

Rule 4.4.2 Put one space after the comment token; right-side comments need ≥ 1 space

Above code: indent comment same as code.
Pick one consistent style.

With //

// Single-line comment
DoSomething();

// Multi-line
// comment
DoSomething();

With /* */

/* Single-line comment */
DoSomething();

/*
 * Alternative multi-line comment
 * second line
 */
DoSomething();

Right-of-code style: 1–4 spaces between code and token.

int foo = 100;  // Right-side comment
int bar = 200;  /* Right-side comment */

Aligning right-side comments is acceptable when cluster is tall:

const int A_CONST = 100;         /* comments may align */
const int ANOTHER_CONST = 200;   /* keep uniform gap */

If the right-side comment would exceed the line limit, move it to the previous standalone line.

Rule 4.4.3 Delete unused code, do not comment it out

Commented-out code cannot be maintained; resurrecting it later risks bugs.
When you no longer need code, remove it. Recoveries use version control.

This applies to /* */, //, #if 0, #ifdef NEVER_DEFINED, etc.

5 Headers

Responsibilities

Headers list a module’s public interface; design is reflected mostly here.
Keep implementation out of headers (inline functions are OK).
Headers should be single-minded; complexity and excessive dependencies hurt compile time.

Advice 5.1.1 Every .cpp file should have a matching .h file for the interface it provides

A .h file declares what the .cpp decides to expose.
If a .cpp publishes nothing to other TUs, it shouldn’t exist—except: program entry point, test code, or DLL edge cases.

Example:

// Foo.h
#ifndef FOO_H
#define FOO_H

class Foo {
public:
    Foo();
    void Fun();

private:
    int value_;
};

#endif
// Foo.cpp
#include "Foo.h"

namespace { // Good: internal helper declared in .cpp, hidden by unnamed namespace or static
    void Bar() {
    }
}

...

void Foo::Fun()
{
    Bar();
}

Header dependencies

Rule 5.2.1 Forbid circular #includes

a.h → b.h → c.h → a.h causes full rebuild on any change.
Refactor architecture to break cycles.

Rule 5.2.2 Every header must have an include guard #define

Do not use #pragma once.

Guidelines:

  1. Use a unique macro per file.
  2. No code/comments except the header license between #ifndef/#define and the matching #endif.

Example: For timer/include/timer.h:

#ifndef TIMER_INCLUDE_TIMER_H
#define TIMER_INCLUDE_TIMER_H
...
#endif

Rule 5.2.3 Forbid extern declarations of external functions/variables

Always #include the proper header instead of duplicating signatures with extern.
Inconsistencies may appear when the real signature changes; also encourages architectural decay.

Bad: // a.cpp

extern int Fun();   // Bad

void Bar()
{
    int i = Fun();
    ...
}

// b.cpp

int Fun() { ... }

Good: // a.cpp

#include "b.h"   // Good
...

// b.h

int Fun();

// b.cpp

int Fun() { ... }

Exception: testing private members, stubs, or patches may use extern when the header itself cannot be augmented.

Rule 5.2.4 Do not #include inside extern “C”

Such includes may nest extern “C” blocks, and some compilers have limited nesting.
Likewise, C/C++ inter-op headers may silently change linkage specifiers.

Example—files a.h / b.h:

// a.h

...
#ifdef __cplusplus
void Foo(int);
#define A(value) Foo(value)
#else
void A(int)
#endif

// b.h

#ifdef __cplusplus
extern "C" {
#endif
#include "a.h"
void B();
#ifdef __cplusplus
}
#endif

After preprocessing b.h in C++:

extern "C" {
    void Foo(int);
    void B();
}

Foo now gets C linkage although author wanted C++ linkage.

Exception: including a pure C header lacking extern "C" inside extern "C" is acceptable when non-intrusive.

Advice 5.2.1 Prefer #include over forward declarations

Forward declaration quick wins:

  1. Saves compile time by reducing header bloat.
  2. Breaks needless rebuilds on unrelated header edits.

Drawbacks:

  1. Hides real dependencies—changes in the header may not trigger recompilation.
  2. Subsequent library changes can break your declaration.
  3. Forward declaring std:: names is undefined by C++11.
  4. Many declarations are longer than a single #include.
  5. Refactoring code just to support forward declaration often hurts performance (pointer members) and clarity.
  6. It is hard to decide when it is truly safe.

Hence, prefer #include to keep dependencies explicit.

6 Scope

Namespaces

Advice 6.1.1 Use anonymous namespaces or static to hide file-local symbols

In C++03 static globals/funcs are deprecated, so unnamed namespaces are preferred.

Reasons:

  1. static already means too many things.
  2. namespace can also encapture types.
  3. Encourage uniform namespace usage.
  4. static functions cannot instantiate templates—namespaces can.

Never use anonymous namespace or static in a .h file.

// Foo.cpp
namespace {
    const int MAX_COUNT = 20;
    void InternalFun();
}

void Foo::Fun()
{
    int i = MAX_COUNT;
    InternalFun();
}

Rule 6.1.1 Never use using namespace in headers or before #includes

Doing so pollutes every translation unit that includes it.
Example:

// a.h

namespace NamespaceA {
    int Fun(int);
}

// b.h

namespace NamespaceB {
    int Fun(int);
}

using namespace NamespaceB;  // Bad

// a.cpp

#include "a.h"
using namespace NamespaceA;
#include "b.h"         // ambiguity: NamespaceA::Fun vs NamespaceB::Fun

Allowed: bringing in single symbols or aliases inside a custom module namespace in headers:

// foo.h

#include <fancy/string>
using fancy::string;                        // Bad in global namespace
namespace Foo {
    using fancy::string;                    // Good
    using MyVector = fancy::vector<int>;    // C++11 type alias OK
}

Global and static member functions

Advice 6.2.1 Prefer namespaces for free functions; use static members only when tightly coupled to a class

Namespace usage avoids global namespace pollution. Only when a free function is intrinsically related to a specific class should it live as a static member.

Helper logic needed only by a single .cpp belongs in an anonymous namespace.

namespace MyNamespace {
    int Add(int a, int b);
}

class File {
public:
    static File CreateTempFile(const std::string& fileName);
};

Global and static member constants

Advice 6.3.1 Use namespaces to hold constants; use static members only when tied to a class

Namespaces guard against global namespace clutter. Only when the constant is logically owned by a specific class should it be a static member.

Implementation-only constants belong in an anonymous namespace.

namespace MyNamespace {
    const int MAX_SIZE = 100;
}

class File {
public:
    static const std::string SEPARATOR;
};

Global variables

Advice 6.4.1 Minimize global variables; use a singleton instead

Mutable globals create tight, invisible coupling across TUs:

int g_counter = 0;

// a.cpp
g_counter++;

// b.cpp
g_counter++;

// c.cpp
cout << g_counter << endl;

Singleton pattern (global unique object):

class Counter {
public:
    static Counter& GetInstance()
    {
        static Counter counter;
        return counter;
    }

    void Increase()   { value_++; }
    void Print() const { std::cout << value_ << std::endl; }

private:
    Counter() : value_(0) {}
    int value_;
};

// a.cpp
Counter::GetInstance().Increase();

// b.cpp
Counter::GetInstance().Increase();

// c.cpp
Counter::GetInstance().Print();

This keeps state shared globally but with better encapsulation.

Exception: if the variable is module-local (each DLL/so or executable instance carries its own), you cannot use a singleton.

7 Classes

Constructors, copy, move, assignment, and destructors

These special members manage object lifetime:

  • Constructor: X()
  • Copy constructor: X(const X&)
  • Copy assignment operator: operator=(const X&)
  • Move constructor: X(X&&) available since C++11
  • Move assignment operator: operator=(X&&) available since C++11
  • Destructor: ~X()

Rule 7.1.1 All member variables of a class must be explicitly initialized

Rationale: If a class has member variables, does not define any constructors, and lacks a default constructor, the compiler will implicitly generate one, but that generated constructor will not initialize the member variables, leaving the object in an indeterminate state.

Exceptions:

  • If the member variable has a default constructor, explicit initialization is not required.

Example: The following code lacks a constructor, so the private data members are uninitialized:

class Message {
public:
    void ProcessOutMsg()
    {
        //…
    }

private:
    unsigned int msgID_;
    unsigned int msgLength_;
    unsigned char* msgBuffer_;
    std::string someIdentifier_;
};

Message message;   // message members are not initialized
message.ProcessOutMsg();   // subsequent use is dangerous

// Therefore, providing a default constructor is necessary, as follows:
class Message {
public:
    Message() : msgID_(0), msgLength_(0), msgBuffer_(nullptr)
    {
    }

    void ProcessOutMsg()
    {
        // …
    }

private:
    unsigned int msgID_;
    unsigned int msgLength_;
    unsigned char* msgBuffer_;
    std::string someIdentifier_; // has a default constructor, no explicit initialization needed
};

Advice 7.1.1 Prefer in-class member initialization (C++11) and constructor initializer lists

Rationale: C++11 in-class initialization makes the default member value obvious at a glance and should be preferred. If initialization depends on constructor parameters or C++11 is unavailable, prefer the initializer list. Compared with assigning inside the constructor body, the initializer list is more concise, performs better, and can initialize const members and references.

class Message {
public:
    Message() : msgLength_(0)  // Good: prefer initializer list
    {
        msgBuffer_ = nullptr;  // Bad: avoid assignment in constructor body
    }
   
private:
    unsigned int msgID_{0};  // Good: use C++11 in-class initialization
    unsigned int msgLength_;
    unsigned char* msgBuffer_;
};

Rule 7.1.2 Declare single-argument constructors explicit to prevent implicit conversions

Rationale: A single-argument constructor not declared explicit acts as an implicit conversion function.
Example:

class Foo {
public:
    explicit Foo(const string& name): name_(name)
    {
    }
private:
    string name_;
};


void ProcessFoo(const Foo& foo){}

int main(void)
{
    std::string test = "test";
    ProcessFoo(test);  // compile fails
    return 0;
}

Compilation fails because ProcessFoo expects a Foo, but a std::string is supplied.

If explicit were removed, the std::string would implicitly convert into a temporary Foo object. Such silent conversions are confusing and may hide bugs. Hence single-argument constructors must be declared explicit.

Rule 7.1.3 Explicitly prohibit copy/move constructs/assignment when not needed

Rationale: Unless the user defines them, the compiler will generate copy constructor, copy assignment operator, move constructor and move assignment operator (move semantics are C++11+).
If the class should not support copying/moving, forbid them explicitly:

  1. Make the copy/move ctor or assignment operator private and leave it undefined:
class Foo {
private:
    Foo(const Foo&);
    Foo& operator=(const Foo&);
};
  1. Use = delete from C++11, see the Modern C++ section.

  2. Prefer inheriting from NoCopyable, NoMovable; disallow macros such as DISALLOW_COPY_AND_MOVE, DISALLOW_COPY, DISALLOW_MOVE.

class Foo : public NoCopyable, public NoMovable {
};

Implementation of NoCopyable and NoMovable:

class NoCopyable {
public:
    NoCopyable() = default;
    NoCopyable(const NoCopyable&) = delete;
    NoCopyable& operator = (NoCopyable&) = delete;
};

class NoMovable {
public:
    NoMovable() = default;
    NoMovable(NoMovable&&) noexcept = delete;
    NoMovable& operator = (NoMovable&&) noexcept = delete;
};

Rule 7.1.4 Provide or prohibit both copy constructor and copy assignment together

Since both operations have copy semantics, allow or forbid them in pairs.

// Both provided
class Foo {
public:
    ...
    Foo(const Foo&);
    Foo& operator=(const Foo&);
    ...
};

// Both defaulted (C++11)
class Foo {
public:
    Foo(const Foo&) = default;
    Foo& operator=(const Foo&) = default;
};

// Both prohibited; in C++11 use `delete`
class Foo {
private:
    Foo(const Foo&);
    Foo& operator=(const Foo&);
};

Rule 7.1.5 Provide or prohibit both move constructor and move assignment together

Move semantics were added in C++11. If a class needs to support moving, both move constructor and move assignment must be present or both deleted.

// Both provided
class Foo {
public:
    ...
    Foo(Foo&&);
    Foo& operator=(Foo&&);
    ...
};

// Both defaulted
class Foo {
public:
    Foo(Foo&&) = default;
    Foo& operator=(Foo&&) = default;
};

// Both deleted
class Foo {
public:
    Foo(Foo&&) = delete;
    Foo& operator=(Foo&&) = delete;
};

Rule 7.1.6 Never call virtual functions in constructors or destructors

Rationale: Calling a virtual function on the object under construction/destruction prevents the intended polymorphic behavior.
In C++, a base class is only building one sub-object at a time.

Example:

class Base {                      
public:               
    Base();
    virtual void Log() = 0;    // Derived classes use different log files
};

Base::Base()         // base constructor
{
    Log();           // virtual call inside ctor
}                                                 

class Sub : public Base {      
public:
    virtual void Log();         
};

When executing Sub sub;, Sub’s ctor runs first but calls Base() first; during Base(), the virtual call to Log binds to Base::Log, not Sub::Log. The same applies in destructors.

Rule 7.1.7 Copy/move constructs/assignment of polymorphic bases must be non-public or deleted

Assigning a derived object to a base object causes slicing: only the base part is copied/moved, breaking polymorphism.
Negative Example:

class Base {                      
public:               
    Base() = default;
    virtual ~Base() = default;
    ...
    virtual void Fun() { std::cout << "Base" << std::endl;}
};

class Derived : public Base {
    ...
    void Fun() override { std::cout << "Derived" << std::endl; }
};

void Foo(const Base &base)
{
    Base other = base; // slicing occurs
    other.Fun(); // calls Base::Fun
}
Derived d;
Foo(d); // derived passed in

Declare copy/move operations delete or private so the compiler rejects such assignments.

Inheritance

Rule 7.2.1 Base class destructors must be virtual; classes not intended for inheritance should be marked final

Rationale: A base destructor must be virtual to ensure derived destructors run when the object is deleted via a base pointer.

Example: A base destructor missing virtual causes memory leaks.

class Base {
public:
    virtual std::string getVersion() = 0;
   
    ~Base()
    {
        std::cout << "~Base" << std::endl;
    }
};
class Sub : public Base {
public:
    Sub() : numbers_(nullptr)
    { 
    }
   
    ~Sub()
    {
        delete[] numbers_;
        std::cout << "~Sub" << std::endl;
    }
   
    int Init()
    {
        const size_t numberCount = 100;
        numbers_ = new (std::nothrow) int[numberCount];
        if (numbers_ == nullptr) {
            return -1;
        }
       
        ...
    }

    std::string getVersion()
    {
        return std::string("hello!");
    }
private:
    int* numbers_;
};
int main(int argc, char* args[])
{
    Base* b = new Sub();

    delete b;
    return 0;
}

Because Base::~Base is not virtual, only its destructor is invoked; Sub::~Sub is skipped and numbers_ leaks.
Exceptions: Marker classes like NoCopyable, NoMovable need neither virtual destructors nor final.

Rule 7.2.2 Virtual functions must not have default arguments

Rationale: In C++, virtual dispatch happens at runtime but default arguments are bound at compile time. The selected function body is from the derived class while its default parameter values come from the base, causing surprising behavior.

Example: the program emits “Base!” instead of the expected “Sub!”

class Base {
public:
    virtual void Display(const std::string& text = "Base!")
    {
        std::cout << text << std::endl;
    }
   
    virtual ~Base(){}
};

class Sub : public Base {
public:
    virtual void Display(const std::string& text  = "Sub!")
    {
        std::cout << text << std::endl;
    }
   
    virtual ~Sub(){}
};

int main()
{
    Base* base = new Sub();
    Sub* sub = new Sub();
  
    ...
   
    base->Display();  // prints: Base!
    sub->Display();   // prints: Sub!
   
    delete base;
    delete sub;
    return 0;
};

Rule 7.2.3 Do not override an inherited non-virtual function

Non-virtual functions are bound statically; only virtual functions provide dynamic dispatch.

Example:

class Base {
public:
    void Fun();
};

class Sub : public Base {
public:
    void Fun();
};

Sub* sub = new Sub();                    
Base* base = sub;

sub->Fun();    // calls Sub::Fun               
base->Fun();   // calls Base::Fun
//...

Multiple Inheritance

Real-world use of multiple inheritance in our code base is rare because it brings several typical problems:

  1. The diamond inheritance issue causes data duplication and name ambiguity; C++ introduces virtual inheritance to address it.
  2. Even without diamond inheritance, name clashes between different bases can create ambiguity.
  3. When a subclass needs to extend/override methods in multiple bases, its responsibilities become unclear, leading to confusing semantics.
  4. Inheritance is white-box reuse: subclasses have access to parents’ protected members, creating stronger coupling. Multiple inheritance compounds the coupling.

Benefits:
Multiple inheritance offers a simpler way to assemble interfaces and behaviors.

Hence multiple inheritance is allowed only in the following cases.

Advice 7.3.1 Use multiple inheritance for interface segregation and multi-role composition

If a class must implement several unrelated interfaces, inherit from separate base classes that represent those roles—similar to Scala traits.

class Role1 {};
class Role2 {};
class Role3 {};

class Object1 : public Role1, public Role2 {
    // ...
};

class Object2 : public Role2, public Role3 {
    // ...
};

The C++ standard library also demonstrates this pattern:

class basic_istream {};
class basic_ostream {};

class basic_iostream : public basic_istream, public basic_ostream {
 
};

Overloading

Overload operators only with good reason and without altering their intuitive semantics; e.g., never use ‘+’ for subtraction.
Operator overloading makes code intuitive, but also has drawbacks:

  • It can mislead readers into assuming built-in efficiency and overlook potential slowdowns;
  • Debugging is harder—finding a function name is easier than locating operator usage.
  • Non-intuitive behaviour (e.g., ‘+’ subtracting) obfuscates code.
  • Implicit conversions triggered by assignment operator overloads can hide deep bugs. Prefer functions like Equals(), CopyFrom() instead of overloading ==, =.

8 Functions

Function Design

Rule 8.1.1 Keep functions short; no more than 50 non-blank non-comment lines

A function should fit on one screen (≤ 50 lines), do one thing, and do it well.
Long functions often indicate mixed concerns, excessive complexity, or missing abstractions.

Exceptions: Algorithmic routines that are inherently cohesive and complete might exceed 50 lines.

Even if the current version works well, future changes may introduce subtle bugs. Splitting into smaller, focused functions eases readability and maintenance.

Inline Functions

Advice 8.2.1 Inline functions should be no more than 10 lines (non-blank non-comment)

Inline functions retain the usual semantics; they just expand in place. Ordinary functions incur call/return overhead; inline functions substitute the body directly.

Inlining only makes sense for very small functions (1–10 lines). For large functions the call-cost is negligible, and compilers usually fall back to normal calls.
Complex control flow (loops, switch, try-catch) normally precludes inlining.

Virtual functions and recursive functions cannot be inlined.

Function Parameters

Advice 8.3.1 Prefer references over pointers for parameters

References are safer: they cannot be null and cannot be reseated after binding; no null pointer checks required.
On legacy platforms follow existing conventions.
Use const to enforce immutability and document the intent, enhancing readability.

Exception: when passing run-time sized arrays, pointers may be used.

Advice 8.3.2 Use strong typing; avoid void*

C/C++ is strongly typed; keep the style consistent. Strong type checking allows the compiler to detect mismatches early.

Using strong types prevents defects. Watch the poorly typed FooListAddNode below:

struct FooNode {
    struct List link;
    int foo;
};

struct BarNode {
    struct List link;
    int bar;
}

void FooListAddNode(void *node) // Bad: using `void *`
{
    FooNode *foo = (FooNode *)node;
    ListAppend(&g_FooList, &foo->link);
}

void MakeTheList()
{
    FooNode *foo = nullptr;
    BarNode *bar = nullptr;
    ...

    FooListAddNode(bar);        // Wrong: meant `foo`, compiles anyway
}
  1. Use template functions for variant parameter types.
  2. Prefer polymorphic base-class pointers/ references.

Advice 8.3.3 Functions shall have no more than 5 parameters

Too many parameters increase coupling to callers and complicate testing.

If you exceed this:

  • Consider splitting the function.
  • Group related parameters into a single struct.

9 Additional C++ Features

Constants and Initialization

Immutable values are easier to understand, trace and analyze; default to const for any definition.

Rule 9.1.1 Do not use macros to define constants

Macros perform simple textual substitution at preprocessing time; error traces and debuggers show raw values instead of names.
Macros lack type checking and scope.

#define MAX_MSISDN_LEN 20    // bad

// use C++ const
const int MAX_MSISDN_LEN = 20; // good

// for C++11+, prefer constexpr
constexpr int MAX_MSISDN_LEN = 20;

Enums are safer than #define or const int; the compiler validates values.

// good:
enum Week {
    SUNDAY,
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY
};

enum Color {
    RED,
    BLACK,
    BLUE
};

void ColorizeCalendar(Week today, Color color);

ColorizeCalendar(BLUE, SUNDAY); // compile-time error: type mismatch

// poor:
const int SUNDAY = 0;
const int MONDAY = 1;

const int BLACK  = 0;
const int BLUE   = 1;

bool ColorizeCalendar(int today, int color);
ColorizeCalendar(BLUE, SUNDAY); // compiles fine

When enum values map to specific constants, assign them explicitly; otherwise omit assignments to avoid redundancy.

// good: device types per protocol S
enum DeviceType {
    DEV_UNKNOWN = -1,
    DEV_DSMP = 0,
    DEV_ISMG = 1,
    DEV_WAPPORTAL = 2
};

Internal enums used only for categorization should not have explicit values.

// good: session states
enum SessionState {
    INIT,
    CLOSED,
    WAITING_FOR_RESPONSE
};

Avoid duplicate values; if unavoidable, qualify them:

enum RTCPType {
    RTCP_SR = 200,
    RTCP_MIN_TYPE = RTCP_SR,       
    RTCP_RR    = 201,
    RTCP_SDES  = 202,
    RTCP_BYE   = 203,
    RTCP_APP   = 204,
    RTCP_RTPFB = 205,
    RTCP_PSFB  = 206,
    RTCP_XR  = 207,
    RTCP_RSI = 208,
    RTCP_PUBPORTS = 209,
    RTCP_MAX_TYPE = RTCP_PUBPORTS 
};

Rule 9.1.2 Forbid magic numbers

“Magic numbers” are literals whose meaning is opaque or unclear.
Clarity is contextual: type = 12 is unclear, whereas monthsCount = yearsCount * 12 is understandable.
Even 0 can be cryptic, e.g., status = 0.

Solution:
Comment locally used literals, or create named const for widely used ones.

Prohibited practices:

  • Names just map a macro to the literal, e.g., const int ZERO = 0
  • Names hard-code limits, e.g., const int XX_TIMER_INTERVAL_300MS = 300; use XX_TIMER_INTERVAL_MS.

Rule 9.1.3 Each constant shall have a single responsibility

A constant must express only one concept; avoid reuse for unrelated purposes.

// good: for two protocols that both allow 20-digit MSISDNs
const unsigned int A_MAX_MSISDN_LEN = 20;
const unsigned int B_MAX_MSISDN_LEN = 20;

// or use distinct namespaces
namespace Namespace1 {
    const unsigned int MAX_MSISDN_LEN = 20;
}

namespace Namespace2 {
    const unsigned int MAX_MSISDN_LEN = 20;
}

Rule 9.1.4 Do not use memcpy_s, memset_s to initialize non-POD objects

POD (“Plain Old Data”) includes primitive types, aggregates, etc., without non-trivial default constructors, virtual functions, or base classes.
Non-POD objects (e.g., classes with virtual functions) have uncertain layouts defined by the ABI; misusing memory routines leads to undefined behavior. Even for aggregates, raw memory manipulation violates information hiding and should be avoided.

See the appendix for detailed POD specifications.

Advice 9.1.2 Declare variables at point of first use and initialize them

Using un-initialized variables is a common bug. Defining variables as late as possible and initializing immediately avoids such errors.

Declaring all variables up front leads to:

  • Poor readability and maintenance: definition sites are far from usage sites.
  • Variables are hard to initialize appropriately: at the beginning of a function, we often lack enough information to initialize them, so we initialize them with some empty default (e.g., zero). That is usually a waste and can also lead to errors if the variable is used before it is assigned a valid value.

Following the principle of minimizing variable scope and declaring variables as close as possible to their first use makes code easier to read and clarifies the type and initial value of every variable. In particular, initialization should be used instead of declaration followed by assignment.

// Bad: declaration and initialization are separated
string name;        // default-constructed at declaration
name = "zhangsan";  // assignment operator called; definition far from declaration, harder to understand

// Good: declaration and initialization are combined, easier to understand
string name("zhangsan");  // constructor called directly

Expressions

Rule 9.2.1 Do not reference a variable again inside the same expression that increments or decrements it

C++ makes no guarantee about the order of evaluation when a variable that undergoes pre/post increment or decrement is referenced again within the same expression. Different compilers—or even different versions of the same compiler—may behave differently.
For portable code, never rely on such unspecified sequencing.

Notice that adding parentheses does not solve the problem, because this is a sequencing issue, not a precedence issue.

Example:

x = b[i] + i++; // Bad: the order of evaluating b[i] and i++ is unspecified.

Write the increment/decrement on its own line:

x = b[i] + i;
i++;            // Good: on its own line

Function arguments

Func(i++, i);   // Bad: cannot tell whether the increment has happened when passing the second argument

Correct:

i++;            // Good: on its own line
x = Func(i, i);

Rule 9.2.2 Provide a default branch in every switch statement

In most cases, a switch statement must contain a default branch so that unlisted cases have well-defined behavior.

Exception: If the control variable is of an enum type and all enumerators are covered by explicit case labels, a default branch is superfluous.
Modern compilers issue a warning when an enumerator in an enum is missing from the switch.

enum Color {
    RED = 0,
    BLUE
};

// Because the switch variable is an enum and all enumerators are covered, we can omit default
switch (color) {
    case RED:
        DoRedThing();
        break;
    case BLUE:
        DoBlueThing();
        ...
        break;
}

Suggestion 9.2.1 When writing comparisons, put the variable on the left and the constant on the right

It is unnatural to read if (MAX == v) or even harder if (MAX > v).
Write the more readable form:

if (value == MAX) {
 
}

if (value < MAX) {
 
}

One exception is range checks: if (MIN < value && value < MAX), where the left boundary is a constant.

Do not worry about accidentally typing = instead of ==; compilers and static-analysis tools will warn about if (value = MAX). Leave typos to the tools; the code must be clear to humans.

Suggestion 9.2.2 Use parentheses to clarify operator precedence

Parentheses make the intended precedence explicit, preventing bugs caused by mismatching the default precedence rules, while simultaneously improving readability. Excessive parentheses, however, can clutter the code. Some guidance:

  • If an expression contains two or more different binary operators, parenthesize it.
x = a + b + c;         /* same operators, parentheses optional */
x = Foo(a + b, c);     /* comma operator, no need for extra parentheses */
x = 1 << (2 + 3);      /* different operators, parentheses needed */
x = a + (b / 5);       /* different operators, parentheses needed */
x = (a == b) ? a : (a  b);    /* different operators, parentheses needed */

Type Casting

Never customize behavior through type branching: that style is error-prone and a clear sign of writing C code in C++. It is inflexible; when a new type is added, every branch must be changed—and the compiler will not remind you. Use templates or virtual functions to let each type determine its behavior, rather than letting the calling code do it.

Avoid type casting; design types that clearly express what kind of object they represent without the need for casting. When designing a numeric type, decide:

  • signed or unsigned
  • float vs double
  • int8, int16, int32, or int64

Inevitably, some casts remain; C++ is close to the machine and talks to APIs whose type designs are not ideal. Still, keep them to a minimum.

Exception: if a function’s return value is intentionally ignored, first reconsider the design. If ignoring it is really appropriate, cast it to (void).

Rule 9.3.1 Use the C++-style casts; never C-style casts

C++ casts are more specific, more readable, and safer. The C++ casts are:

  • For type conversions

    1. dynamic_cast — down-cast in an inheritance hierarchy and provides run-time type checking. Avoid it by improving base/derived design instead.
    2. static_cast — like C-style but safer. Used for value conversions or up-casting, and resolving multiple-inheritance ambiguities. Prefer brace-init for pure arithmetic.
    3. reinterpret_cast — reinterprets one type as another. Undefined behaviour potential, use sparingly.
    4. const_cast — removes const or volatile. Suppresses immutability, leading to UB if misused.
  • Numeric conversions (C++11 onwards)
    Use brace-init for converting between numeric types without loss.

  double d{ someFloat };
  int64_t i{ someInt32 };

Suggestion 9.3.1 Avoid dynamic_cast

  1. dynamic_cast depends on RTTI, letting programs discover an object’s concrete type at run time.
  2. Its necessity usually signals a flaw in the class hierarchy; prefer redesigning classes instead.

Suggestion 9.3.2 Avoid reinterpret_cast

reinterpret_cast performs low-level re-interpretations of memory layouts and is inherently unsafe. Avoid crossing unrelated type boundaries.

Suggestion 9.3.3 Avoid const_cast

const_cast strips the const and volatile qualifiers off an object. Modifying a formerly const variable via such a cast yields Undefined Behaviour.

// Bad
const int i = 1024;
int* p = const_cast<int*>(&i);
*p = 2048;      // Undefined Behaviour
// Bad
class Foo {
public:
    Foo() : i(3) {}

    void Fun(int v) { i = v; }

private:
    int i;
};

int main() {
    const Foo f;
    Foo* p = const_cast<Foo*>(&f);
    p->Fun(8);  // Undefined Behaviour
}

Resource Acquisition and Release

Rule 9.4.1 Use delete for single objects and delete[] for arrays

  • single object: new allocates and constructs exactly one object ⇒ dispose with delete.
  • array: new[] allocates space for (n) objects and constructs them ⇒ dispose with delete[].

Mismatching new/new[] with the wrong form of delete yields UB.

Wrong:

const int MAX_ARRAY_SIZE = 100;
int* numberArray = new int[MAX_ARRAY_SIZE];
...
delete numberArray;        // ← wrong

Right:

const int MAX_ARRAY_SIZE = 100;
int* numberArray = new int[MAX_ARRAY_SIZE];
...
delete[] numberArray;

Suggestion 9.4.1 Use RAII to track dynamic resources

RAII = Resource Acquisition Is Initialization. Acquire a resource in a constructor; the destructor releases it. Benefits:

  • No manual cleanup.
  • The resource remains valid for the scope’s lifetime.

Example: mutex guard

class LockGuard {
public:
    LockGuard(const LockType& lock) : lock_(lock) { lock_.Acquire(); }
    ~LockGuard() { lock_.Release(); }
private:
    LockType lock_;
};

bool Update() {
    LockGuard g(mutex);
    ...
}

Standard Library

STL usage varies between products; basic rules below.

Rule 9.5.1 Never store the pointer returned by std::string::c_str()

string::c_str() is not guaranteed to point at stable memory. A specific implementation may return memory that is released soon after the call. Therefore, call string::c_str() at point of use; do not store the pointer.

Example:

void Fun1() {
    std::string name = "demo";
    const char* text = name.c_str();   // still valid here
    name = "test";                     // may invalidate text
    ...
}

Exception: in extremely performance-critical code it is acceptable to store the pointer, provided the std::string is guaranteed to outlive the pointer and remain unmodified while the pointer is used.

Suggestion 9.5.1 Prefer std::string over char*

Advantages:

  • no manual null-termination
  • built-in operators (+, =, ==)
  • automatic memory management

Be aware: some legacy STL implementations use Copy-On-Write (COW) strings. COW is not thread-safe and can cause crashes. Passing COW strings across DLL boundaries can leave dangling references. Choose a modern, non-COW STL when possible.

Exception: APIs that require 'char*' get it from std::string::c_str(). Stack buffers should be plain char arrays, not std::string or std::vector<char>.

Rule 9.5.2 Do not use auto_ptr

std::auto_ptr performs implicit (and surprising) ownership transfer:

auto_ptr<T> p1(new T);
auto_ptr<T> p2 = p1;   // p1 becomes nullptr

Therefore it is forbidden.
Use std::unique_ptr for exclusive ownership, std::shared_ptr for shared ownership.

Suggestion 9.5.2 Prefer canonical C++ headers

Use <cstdlib> instead of <stdlib.h>, etc.

const Qualifier

Add const to variables and parameters whose values must not change. Mark member functions const if they do not modify the observable state.

Rule 9.6.1 Use const for pointer/reference parameters that are read-only

Write:

void PrintFoo(const Foo& foo);

instead of Foo&.

Rule 9.6.2 Mark member functions const when they do not modify the object

Accessors should always be const.

class Foo {
public:
    int PrintValue() const;
    int GetValue() const;
private:
    int value_;
};

Suggestion 9.6.1 Declare member variables const when they never change after initialization

class Foo {
public:
    Foo(int length) : dataLength_(length) {}
private:
    const int dataLength_;
};

Exceptions

Suggestion 9.7.1 Mark functions that never throw as noexcept (C++11)

Benefits:

  1. Enables better code generation.
  2. Standard containers only use move-construction when a move operator is noexcept. Without noexcept they fall back to (slower) copy-construction.

Example:

extern "C" double sqrt(double) noexcept;

std::vector<int> Compute(const std::vector<int>& v) noexcept {
    ...
}

Destructor, default constructors, swap, and move operations must never throw.

Templates & Generic Programming

Rule 9.8.1 Prohibit generic programming in the OpenHarmony project

Generic programming, templates, and OOP are driven by entirely different ideas and techniques. OpenHarmony is mainly based on OOP.

Avoid templates because:

  1. Inexperienced developers tend to produce bloated, confusing code.
  2. Template code is hard to read and debug.
  3. Error messages are notoriously cryptic.
  4. Templates may generate excessive code size.
  5. Refactoring template code is difficult because its instantiations are spread across the codebase.

OpenHarmony forbids template programming in most modules.
Exception: STL adaptation layers may still use templates.

Macros

Avoid complex macros. Instead:

  • Use const or enums for constants.
  • Replace macro functions with inline or template functions.
// Bad
#define SQUARE(a, b) ((a) * (b))

// Prefer
template<typename T>
T Square(T a, T b) { return a * b; }

10 Modern C++ Features

ISO standardized C++11 in 2011 and C++17 in March 2017. These standards add countless improvements. This chapter highlights best practices for using them effectively.

Brevity & Safety

Suggestion 10.1.1 Use auto judiciously

  • Eliminates long type names, guarantees initialization.
  • Deduction rules are subtle—understand them.
  • Prefer explicit types if clarity improves. Use auto only for local variables.

Examples:

auto iter = m.find(val);
auto p = new Foo;

int x;    // uninitialized
auto x;   // compile-time error—uninitialized

Beware deduced types:

std::vector<std::string> v;
auto s1 = v[0];   // std::string, makes a copy

Rule 10.1.1 Override virtual functions with override or final

They ensure correctness: the compiler rejects overrides whose signatures do not match the base declaration.

class Base {
    virtual void Foo();
};
class Derived : public Base {
    void Foo() override;  // OK
    void Foo() const override; // Error: signature differs
};

Rule 10.1.2 Delete functions with the delete keyword

Clearer and broader than making members private and undefined.

Foo& operator=(const Foo&) = delete;

Rule 10.1.3 Prefer nullptr to NULL or 0

nullptr has its own type (std::nullptr_t) and unambiguous behaviour; 0/NULL cannot. Or, when a null pointer is required, directly using 0 can introduce another problem: the code becomes unclear—especially when auto is used:

auto result = Find(id);
if (result == 0) {  // Does Find() return a pointer or an integer?
    // do something
}

Literally, 0 is of type int (0L is long), so neither NULL nor 0 is of a pointer type.
When a function is overloaded for both pointer and integer types, passing NULL or 0 will invoke the integer overload:

void F(int);
void F(int*);

F(0);      // Calls F(int), not F(int*)
F(NULL);   // Calls F(int), not F(int*)

Moreover, sizeof(NULL) == sizeof(void*) is not necessarily true, which is another potential pitfall.

Summary: directly using 0 or 0L makes the code unclear and type-unsafe; using NULL is no better than 0 because it is also type-unsafe. All of them involve potential risks.

The advantage of nullptr is not just being a literal representation of the null pointer that clarifies the code, but also that it is definitively not an integer type.

nullptr is of type std::nullptr_t, and std::nullptr_t can be implicitly converted to any raw pointer type, so nullptr can act as the null pointer for any type.

void F(int);
void F(int*);
F(nullptr);   // Calls F(int*)

auto result = Find(id);
if (result == nullptr) {  // Find() returns a pointer
    // do something
}

Rule 10.1.4: Prefer using over typedef

Prior to C++11, you could define type aliases with typedef; nobody wants to repeatedly type std::map<uint32_t, std::vector<int>>.

typedef std::map<uint32_t, std::vector<int>> SomeType;

An alias is essentially an encapsulation of the real type. This encapsulation makes code clearer and prevents shotgun surgery when the underlying type changes.

Since C++11, using provides alias declarations:

using SomeType = std::map<uint32_t, std::vector<int>>;

Compare their syntaxes:

typedef Type Alias;   // Is Type first or Alias first?
using Alias = Type;   // Reads like assignment—intuitive and error-proof

If that alone isn’t enough to adopt using, look at alias templates:

// Alias template definition—one line
template<class T>
using MyAllocatorVector = std::vector<T, MyAllocator<T>>;

MyAllocatorVector<int> data;       // Using the alias

template<class T>
class MyClass {
private:
    MyAllocatorVector<int> data_;   // Alias usable inside a template
};

typedef does not support template parameters in aliases; workarounds are required:

// Need to wrap typedef in a template struct
template<class T>
struct MyAllocatorVector {
    typedef std::vector<T, MyAllocator<T>> type;
};

MyAllocatorVector<int>::type data;  // Using typedef—must append ::type

template<class T>
class MyClass {
private:
    typename MyAllocatorVector<int>::type data_;  // Need typename too
};

Rule 10.1.5: Do not apply std::move to const objects

Semantically, std::move is about moving an object. A const object cannot be modified and is therefore immovable, so applying std::move confuses readers.

Functionally, std::move yields an rvalue reference; for const objects this becomes const&&. Very few types provide move constructors or move-assignment operators taking const&&, so the operation usually degrades to a copy, harming performance.

Bad:

std::string g_string;
std::vector<std::string> g_stringList;

void func()
{
    const std::string myString = "String content";
    g_string = std::move(myString); // Bad: copies, does not move
    const std::string anotherString = "Another string content";
    g_stringList.push_back(std::move(anotherString)); // Bad: also copies
}

Smart Pointers

Rule 10.2.1: Prefer raw pointers for singletons, data members, etc., whose ownership is never shared

Rationale
Smart pointers prevent leaks by automatically releasing resources but add overhead—code bloat, extra construction/destruction cost, increased memory footprint, etc.

For objects whose ownership is never transferred (singletons, data members), simply deleting them in the destructor is sufficient; avoid the extra smart-pointer overhead.

Example

class Foo;
class Base {
public:
    Base() {}
    virtual ~Base()
    {
        delete foo_;
    }
private:
    Foo* foo_ = nullptr;
};

Exceptions

  1. When returning newly created objects that need custom deleters, smart pointers are acceptable.
class User;
class Foo {
public:
    std::unique_ptr<User, void(User *)> CreateUniqueUser()
    {
        sptr<User> ipcUser = iface_cast<User>(remoter);
        return std::unique_ptr<User, void(User *)>(::new User(ipcUser), [](User *user) {
            user->Close();
            ::delete user;
        });
    }

    std::shared_ptr<User> CreateSharedUser()
    {
        sptr<User> ipcUser = iface_cast<User>(remoter);
        return std::shared_ptr<User>(ipcUser.GetRefPtr(), [ipcUser](User *user) mutable {
            ipcUser = nullptr;
        });
    }
};
  1. Use shared_ptr when returning a newly created object that can have multiple owners.

Rule 10.2.2: Use std::make_unique, not new, to create a unique_ptr

Rationale

  1. make_unique is more concise.
  2. It provides exception safety in complex expressions.

Example

// Bad: type appears twice—possible inconsistency
std::unique_ptr<MyClass> ptr(new MyClass(0, 1));
// Good: type appears only once
auto ptr = std::make_unique<MyClass>(0, 1);

Repetition can cause subtle bugs:

// Compiles, but mismatched new[] and delete
std::unique_ptr<uint8_t> ptr(new uint8_t[10]);
std::unique_ptr<uint8_t[]> ptr(new uint8_t);

// Not exception-safe: evaluation order may be
// 1. allocate Foo
// 2. construct Foo
// 3. call Bar
// 4. construct unique_ptr<Foo>
// If Bar throws, Foo leaks.
F(unique_ptr<Foo>(new Foo()), Bar());

// Exception-safe: no interleaving
F(make_unique<Foo>(), Bar());

Exception
std::make_unique does not support custom deleters.
Fall back to new only when a custom deleter is required.

Rule 10.2.4: Use std::make_shared, not new, to create a shared_ptr

Rationale
Besides the same consistency benefits as make_unique, make_shared offers performance gains.
A shared_ptr manages two entities:

  • Control block (ref-count, deleter, etc.)
  • The owned object

make_shared allocates one heap block for both, whereas
std::shared_ptr<MyClass>(new MyClass) performs two allocations: one for MyClass and one for the control block, adding overhead.

Exception
Like make_unique, make_shared cannot accept a custom deleter.


Lambda Expressions

Advice 10.3.1: Prefer lambda expressions when a function cannot express what you need (capture locals, local function)

Rationale
Functions cannot capture locals nor be declared in local scope. When you need such features, use lambda rather than hand-written functors.
Conversely, lambdas and functors can’t be overloaded; overloadable cases favor functions.
When both lambdas and functions work, prefer functions—always reach for the simplest tool.

Example

// Overloads for int and string—natural to choose
void F(int);
void F(const string&);

// Needed: capture locals or appear inline
vector<Work> v = LotsOfWork();
for (int taskNum = 0; taskNum < max; ++taskNum) {
    pool.Run([=, &v] {...});
}
pool.Join();

Rule 10.3.1: Return or store lambdas outside local scope only by value capture; never by reference

Rationale
A non-local lambda (returned, stored on heap, passed to another thread) must not hold dangling references; avoid capture by reference.

Example

// Bad
void Foo()
{
    int local = 42;
    // Capture by reference; `local` dangles after return
    threadPool.QueueWork([&]{ Process(local); });
}

// Good
void Foo()
{
    int local = 42;
    // Capture by value
    threadPool.QueueWork([=]{ Process(local); });
}

Advice 10.3.2: If you capture this, write all other captures explicitly

Rationale
Inside a member function [=] looks like capture-by-copy, but it implicitly captures this by copy, yielding handles to every data member (i.e., reference semantics in disguise). When you really want that, write it explicitly.

Example

class MyClass {
public:
    void Foo()
    {
        int i = 0;

        auto Lambda = [=]() { Use(i, data_); };   // Bad: not a true copy of data_

        data_ = 42;
        Lambda();  // Uses 42
        data_ = 43;
        Lambda();  // Uses 43; shows reference semantics

        auto Lambda2 = [i, this]() { Use(i, data_); };   // Good: explicit, no surprises
    }

private:
    int data_ = 0;
};

Advice 10.3.3: Avoid default capture modes

Rationale
Lambdas support default-by-reference (&) and default-by-value (=).
Default-by-reference silently binds every local variable; easy to create dangling refs.

Default-by-value implicitly captures this and hides which variables are actually used; readers may mistakenly believe static variables are copied too.

Therefore always list the captures explicitly.

Bad

auto func()
{
    int addend = 5;
    static int baseValue = 3;

    return [=]() {           // only copies addend
        ++baseValue;         // modifies global
        return baseValue + addend;
    };
}

Good

auto func()
{
    int addend = 5;
    static int baseValue = 3;

    return [addend, baseValue = baseValue]() mutable {   // C++14 capture-init
        ++baseValue;        // modifies local copy
        return baseValue + addend;
    };
}

Reference: Effective Modern C++, Item 31: Avoid default capture modes.


Interfaces

Advice 10.4.1: In interfaces not concerned with ownership, pass T* or T&, not smart pointers

Rationale

  1. Smart pointers transfer or share ownership only when needed.
  2. Requiring smart pointers forces callers to use them (e.g., impossible to pass this).
  3. Passing shared-ownership smart pointers incurs runtime overhead.

Example

// Accepts any int*
void F(int*);

// Accepts only when ownership is to be transferred
void G(unique_ptr<int>);

// Accepts only when ownership is to be shared
void G(shared_ptr<int>);

// Ownership unchanged, but caller must hold a unique_ptr
void H(const unique_ptr<int>&);

// Accepts any int
void H(int&);

// Bad
void F(shared_ptr<Widget>& w)
{
    // ...
    Use(*w); // lifetime not relevant
    // ...
}