@@ -1455,4 +1455,2185 @@ templates and is not meant for its definition.
with a forward declaration may cause unexpected results.
Therefore, we should avoid using forward declarations as much as possible. Instead, we use the #include statement to include a header
file and ensure dependency.
\ No newline at end of file
file and ensure dependency.
Scopes
******
Namespaces
==========
Recommendation 6.1.1 For code that does not need to be exported from the .cpp file, you are advised to use an unnamed namespace for encapsulation or use static to modify the variables, constants, or functions that need hiding.
In the C++ 2003 standard, using static to modify the external availability of functions and variables was marked as deprecated.
Therefore, unnamed namespaces are the recommended method.
Main Reasons:
#. There are too many meanings for static in C++: static function member variable, static member function, static global variable, and static function local variable. Each of them has specialprocessing.
#. Static can only be used to define variables, constants, and functions that are not referenced outside the current .cpp file, while namespaces can also be used to encapsulate types.
#. Use a namespace to control the scope instead of using both static and namespaces.
#. Unnamed namespaces can be used to instantiate templates rather than functions modified by the static keyword.
Do not use unnamed namespaces or static in header files.
.. code:: cpp
// Foo.cpp
namespace {
const int MAX_COUNT = 20;
void InternalFun(){};
}
void Foo::Fun()
{
int i = MAX_COUNT;
InternalFun();
}
Rule 6.1.1 Do not use “using” to import namespace in a header file or before #include statements.
std::string someIdentifier; // The member variable has a default constructor. Therefore, explicit initialization is not required.
};
Recommendation 7.1.1 Initialization during declaration (C++ 11) and initialization using the constructor initialization list are preferred for member variables.
Note: If a single-parameter constructor is not declared as explicit, it
will become an implicit conversion function. Example:
.. code:: cpp
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); // Compiling failed.
return 0;
}
The preceding code fails to be compiled because the parameter required by ``ProcessFoo`` is of the Foo type, which mismatch with the input string type.
If the explicit keyword of the Foo constructor is removed, implicit conversion is triggered and a temporary Foo object is generated when ``ProcessFoo`` is called with the string parameter. Usually, this
implicit conversion is confusing and bugs are apt to be hidden, due to unexpected type conversion. Therefore, single-parameter constructors require explicit declaration.
Rule 7.1.3 If copy/move constructors and copy/move assignment operators are not needed, clearly prohibit them.
Note: If users do not define it, the compiler will generate copy constructors and copy assignment operators, move constructors and move assignment operators (move semantic functions will be available in versions later than C++ 11). If we do not use copy constructors or copy
assignment operators, explicitly delete them.
#. Set copy constructors or copy assignment operators to private and do
not implement them.
.. code:: cpp
class Foo {
private:
Foo(const Foo&);
Foo& operator=(const Foo&);
};
#. Use delete provided by C++ 11. For details, see Rule 10.1.3 in chapter 10 Modern C++ Features.
Rule 7.1.4 Copy constructors and copy assignment operators should be implemented or forbidden together.
Note: Non-virtual functions cannot be dynamically bound (only virtual
functions can be dynamically bound). You can obtain the correct result
by operating on the pointer of the base class.
Example:
.. code:: cpp
class Base {
public:
void Fun();
};
class Sub : public Base {
public:
void Fun();
};
Sub* sub = new Sub();
Base* base = sub;
sub->Fun(); // Call Fun of the derived class.
base->Fun(); // Call Fun of the base class.
//...
Multiple Inheritance
====================
In the actual development process, multiple inheritance is seldom used because the following typical problems may occur:
#. Data duplication and name ambiguity caused by “diamond” inheritance. C++ introduces virtual inheritance to solve these problems.
#. In addition to “diamond” inheritance, names of multiple base classes may also conflict with each other, resulting in name ambiguity.
#. If a derived class needs to be extended or needs to override methods of multiple base classes, the responsibilities of the derived classes are
unclear and semantics are muddled.
#. Compared with delegation, inheritance is seen as white box reuse, that is, a derived class can access the protected members of the base class,
which leads to more coupling. Multiple inheritance, due to the coupling of multiple base classes, leads to even more coupling.
Multiple inheritance has the following advantages:
Multiple inheritance provides a simpler method for assembling and reusing multiple interfaces or classes. Therefore, multiple inheritance can be used only in the following cases:
Recommendation 7.3.1 Use multiple inheritance to implement interface separation and multi-role combination.
A function should be displayed on one screen (no longer than 50 lines). It should do only one thing, and do it well.
Long functions often mean that the functions are too complex to implement in more than one function, or overly detailed but not further
abstracted.
Exception: Some algorithms may be longer than 50 lines due to algorithm convergence and functional comprehensiveness.
Even if a long function works very well now, once someone modifies it, new problems may occur. It might even cause bugs that are difficult to
find. It is recommended that you split a long function into several functions that are simpler and easier to manage, facilitating comprehension and modification.
Inline Functions
================
Recommendation 8.2.1 An inline function cannot exceed 10 lines.
**Note**: An inline function has the same characteristics of a normal function. The difference between an inline function and a normal function lies in the processing of function calls. When a general function is called, the program execution right is transferred to the
called function, and then returned to the function that calls it. When an inline function is called, the invocation expression is replaced with an inline function body.
Inline functions are only suitable for small functions with only 1-10 lines. For a large function that contains many statements, the function call and return overheads are relatively trivial and do not need the
help of an inline function. Most compilers may abandon the inline mode and use the common method to call the function.
If an inline function contains complex control structures, such as loop, branch (switch), and try-catch statements, the compiler may regard the function as a common function. **Virtual functions and recursive
functions cannot be used as inline functions**.
Function Parameters
===================
Recommendation 8.3.1 Use a reference instead of a pointer for function parameters.
**Note**: A reference is more secure than a pointer because it is not empty and does not point to other targets. Using a reference stops the need to check for illegal null pointers.
If a product is being developed for an older platform, the processing used by the old platform is preferred. Use const to avoid parameter modification, so that readers can clearly know that a parameter is not going to be modified. This greatly enhances code readability.
Exception: When the input parameter is an array with an unknown compile-time length, you can use a pointer instead of a reference.
Recommendation 8.3.2 Use strongly typed parameters. Do not use void*.
While different languages have their own views on strong typing and weak typing, it is generally believed that C and C++ are strongly typed languages. Since we use such a strongly typed language, we should keep to this style. An advantage of this is the compiler can find type mismatch problems at the compilation stage.
Using strong typing helps the compiler find more errors for us. Pay attention to the usage of the FooListAddNode function in the following code:
.. code:: cpp
struct FooNode {
struct List link;
int foo;
};
struct BarNode {
struct List link;
int bar;
}
void FooListAddNode(void *node) // Bad: Here, the void * type is used to transfer parameters.
{
FooNode *foo = (FooNode *)node;
ListAppend(&g_FooList, &foo->link);
}
void MakeTheList()
{
FooNode *foo = NULL;
BarNode *bar = NULL;
...
FooListAddNode(bar); // Wrong: In this example, the foo parameter was supposed to be transferred, but the bar parameter is accidentally transferred instead. However, no error is reported.
}
#. You can use a template function to change the parameter type.
#. A base class pointer can be used to implement this according to
polymorphism.
Recommendation 8.3.3 A function can have a maximum of five parameters.
Note: The C++ standard does not specify that the string::c_str ()
pointer is permanently valid. Therefore, the STL implementation used can
return a temporary storage area and release it quickly when calling
string::c_str (). Therefore, to ensure the portability of the program,
do not save the result of string::c_str (). Instead, call it directly.
Example:
.. code:: cpp
void Fun1()
{
std::string name = "demo";
const char* text = name.c_str(); // After the expression ends, the life cycle of name is still in use and the pointer is valid.
// If a non-const member function (such as operator[] and begin()) of the string type is invoked and the string is modified,
// The text may become unavailable or may not be the original string.
name = "test";
name[1] = '2';
// When the text pointer is used next time, the string is no longer "demo".
}
void Fun2()
{
std::string name = "demo";
std::string test = "test";
const char* text = (name + test).c_str(); // After the expression ends, the temporary object generated by the + operator may be destroyed, and the pointer may be invalid.
// When the text pointer is used next time, it no longer points to the valid memory space.
}
Exception: In rare cases where high performance coding is required , you
can temporarily save the pointer returned by string::c_str() to match
the existing functions which support only the input parameters of the
const char\* type. However, you should ensure that the lifecycle of the
string object is longer than that of the saved pointer, and that the
string object is not modified within the lifecycle of the saved pointer.
Recommendation 9.5.1 Use std::string instead of char*.
void Foo(int var) final; // Compilation successful: Derived::Foo(int) rewrites Base::Foo(int), and the derived class of Derived cannot override this function.
void Bar() override; // Compilation failed: base::Bar is not a virtual function.
};
**Summary** 1. When defining the virtual function for the first time
based on the base class, use the keyword ``virtual``. 2. When overriding
the virtual function by a subclass in a base class, including
destructors, use the keyword ``override`` or ``final`` instead of
``virtual``. 3. For the non-virtual function, do not use ``virtual`` or
``override``.
Rule: 10.1.2 Use the keyword ``delete`` to delete functions.
**Reason:** The ``[=]`` in the member function seems to indicate
capturing by value but actually it is capturing data members by
reference because it captures the invisible ``this`` pointer by value.
Generally, it is recommended that capturing by reference be avoided. If
it is necessary to do so, write ``this`` explicitly.
**Example:**
.. code:: cpp
class MyClass {
public:
void Foo()
{
int i = 0;
auto Lambda = [=]() { Use(i, data_); }; // Bad: It looks like we are copying or capturing by value but member variables are actually captured by reference.
data_ = 42;
Lambda(); // Call use(42);
data_ = 43;
Lambda(); // Call use(43);
auto Lambda2 = [i, this]() { Use(i, data_); }; // Good: the most explicit and least confusing method.