01 C++ Programming Guidelines – Constants02
Initialization and Type Conversion
Declaration, Definition, and Initialization
03
Do not use memcpy or memset to initialize non-POD objects
Note: POD stands for “Plain Old Data”, a concept introduced in the C++98 standard (ISO/IEC 14882, first edition, 1998-09-01). POD types mainly include int, char, float, double, enumeration, void, and pointers and other primitive types and their aggregate types, which cannot use encapsulation and object-oriented features (such as user-defined constructors/assignment/destructors, base classes, virtual functions, etc.).Since non-POD types, such as non-aggregate class objects, may have virtual functions and uncertain memory layouts that depend on the compiler, misuse of memory copying can lead to serious issues. Even for aggregate types, using direct memory copying and comparison undermines information hiding and data protection, so memcpy and memset operations are also discouraged.Example: The product program crashed (core dump). After simulating the on-site environment, the program generated a COREDUMP due to: using
memset(this,0,sizeof(*this))
for class initialization, which cleared the class’s virtual function table pointer, leading to the use of a null pointer.Solution: Use C++ constructors for initialization, do not use memset to initialize class objects.04
Declare and initialize variables only when they are used
Note: Failing to assign an initial value to a variable before use is a common low-level programming error. Declaring and initializing variables only when they are used conveniently avoids such low-level errors. Declaring all variables at the beginning of a function and using them later, with a scope covering the entire function implementation, can lead to the following issues:⚫ Difficult to understand and maintain: Separation of variable definition and usage.⚫ Difficult to initialize variables reasonably: At the beginning of a function, there is often insufficient information to initialize variables, often leading to the use of some default empty value (like zero) for initialization, which is usually wasteful. If a variable is used before being assigned a valid value, it can lead to errors. Following the principles of minimizing variable scope and declaring close to usage makes the code easier to read and understand the variable’s type and initial value. In particular, initialization should replace declaration followed by assignment.Example:
// Bad example: separation of declaration and initialization
string name; // Not initialized at declaration: calls default constructor
//.......
name="zhangsan"; // Calls assignment operator function again; declaration and definition are in different places, making it relatively difficult to understand
// Good example: declaration and initialization together, easier to understand
string name("zhangsan"); // Calls constructor once
05
Avoid complex initialization in constructors; use an “init” function instead
Note: Just as function variables are initialized within the function, the best place to initialize class data members is in the constructor, and data members should be initialized as much as possible in the constructor.The following situations can use the init() function for initialization:⚫ Need to provide initialization return information.⚫ Data member initialization may throw exceptions.⚫ Failure of data member initialization can cause the class object initialization to fail, leading to an uncertain state.⚫ Data member initialization depends on the this pointer: the constructor has not finished, and the object has not been constructed, so this members cannot be used in the constructor;⚫ Data member initialization requires calling virtual functions. Calling virtual functions in constructors and destructors can lead to undefined behavior.Example: Data member initialization may throw exceptions:
class CPPRule {public: CPPRule():size_(0), res (null) {}; // Only value initialization long init(int size) { // Initialize size_ and allocate resource res based on the passed parameter } private: int size_; ResourcePtr* res; }; // Usage: CPPRule a; a.init(100);
06
Initialization lists must strictly follow the order of member declarations
Note: The compiler initializes data members in the order they are declared in the class definition, not in the order of the initialization list. If the order of the initialization list is disrupted, it will not actually work but will cause confusion in reading and understanding; especially when there are dependencies between member variables, it may lead to bugs.
Example:
// Bad example: initialization order does not match declaration order
class Employee { public: Employee(const char* firstName, const char* lastName) : firstName_(firstName), lastName_(lastName) , email_(firstName_ + "." + lastName_ + "@huawei.com") {}; private: string email_, firstName_, lastName_; };
In the class definition, email_ is declared before firstName_ and lastName_, so it will be initialized first, but it uses uninitialized firstName_ and lastName_, leading to errors. Member declarations should be ordered according to their interdependencies.
07 Type ConversionAvoid using type branching to customize behavior: Type branching to customize behavior is prone to errors and is a clear sign of attempting to write C code in C++.This is a very inflexible technique; when adding new types, if you forget to modify all branches, the compiler will not notify you. Use templates and virtual functions to let types decide behavior themselves rather than the code that calls them.08
Use C++ style type conversions, do not use C style type conversions
Note: C++ type conversions are more prominent due to the use of keywords, making them easier to find, forcing programmers to think a moment longer and use casts cautiously.C++ uses const_cast, dynamic_cast, static_cast, reinterpret_cast and other new type conversions that allow users to choose the appropriate level of conversion operator, rather than using one conversion operator like in C.dynamic_cast: Mainly used for downcasting, dynamic_cast has type checking capabilities. dynamic_cast has some overhead and is recommended for use in debugging code.
#include <iostream>
#include<typeinfo>
class Base {public: virtual ~Base() {} // Requires virtual function to enable RTTI};
class Derived : public Base {public: void derived_func() { std::cout << "Derived function called" << std::endl; }};
int main() { Base* base_ptr = new Derived(); // Use dynamic_cast for safe downcasting Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr); if (derived_ptr) { std::cout << "Conversion successful" << std::endl; derived_ptr->derived_func(); // Safe call to derived class function } else { std::cout << "Conversion failed" << std::endl; } // Attempt to convert mismatched types Derived* another_ptr = dynamic_cast<Derived*>(new Base()); if (another_ptr) { std::cout << "Conversion successful" << std::endl; } else { std::cout << "Conversion failed" << std::endl; } delete base_ptr; delete another_ptr; return 0;}
For reference types of dynamic_cast:
#include<iostream>
#include <typeinfo>
class Base {public: virtual ~Base() {}};
class Derived : public Base {public: void derived_func() { std::cout << "Derived function called" << std::endl; }};
void process(Base& base) { try { Derived& derived = dynamic_cast<Derived>(base); std::cout << "Conversion successful" << std::endl; derived.derived_func(); } catch (const std::bad_cast& e) { std::cout << "Conversion failed: " << e.what() << std::endl; }}
int main() { Derived derived; Base base; std::cout << "Processing Derived object:" << std::endl; process(derived); // Conversion successful std::cout << "\nProcessing Base object:" << std::endl; process(base); // Conversion failed return 0;}
static_cast: Similar to C style casting, it can perform value coercion or upcasting (converting a derived class pointer or reference to a base class pointer or reference). This conversion is often used to eliminate type ambiguity caused by multiple inheritance and is relatively safe. Downcasting (converting a base class pointer or reference to a derived class pointer or reference) is unsafe due to the lack of dynamic type checking and is not recommended.Upcasting (derived class to base class) – this is one of the safest uses of static_cast
class Base {public: virtual ~Base() {} virtual void base_func() { std::cout << "Base function" << std::endl; }};
class Derived : public Base {public: void derived_func() { std::cout << "Derived function" << std::endl; }};
int main() { Derived derived; Base* base_ptr = static_cast<Base*>(&derived); // Upcasting, safe base_ptr->base_func(); // Call base class function return 0;}
reinterpret_cast: Used to convert unrelated types. reinterpret_cast forces the compiler to reinterpret the memory of an object of one type as another type, and related code may not be portable. It is recommended to comment on the use of reinterpret_cast to help reduce maintainers’ concerns when they see such conversions.
Good coding practice: Add comments
// Note: Here, reinterpret_cast is used because we need to interpret a specific memory area
// as another type, which is platform-dependent and not portable.
class MemoryMappedDevice {public: MemoryMappedDevice(void* base_addr) : control(reinterpret_cast<volatile uint32_t*>(base_addr)), data(reinterpret_cast<volatile uint32_t*>(static_cast<uintptr_t>(base_addr) + 4)) {}private: volatile uint32_t* control; volatile uint32_t* data;};
const_cast: Used to remove the const property of an object, making it modifiable.
int main() { const int ci = 10; const int* ci_ptr = &ci // Remove const property int* i_ptr = const_cast<int*>(ci_ptr); *i_ptr = 20; // Now can modify value std::cout << "ci: " << ci << std::endl; // Output may be 20 or still 10 std::cout << "*i_ptr: " << *i_ptr << std::endl; // Output 20 return 0;}
09
extern void Fun(DerivedClass* pd); void Gun(BaseClass* pb) { // Bad example: C style cast, conversion may lead to inconsistent object layout, compiler does not report error, runtime may crash
DerivedClass* pd = (DerivedClass *)pb; // Good example: C++ style cast, clearly know pb actually points to DerivedClass
DerivedClass* pd = dynamic_cast< DerivedClass *>(pb); if(pd) Fun(pd);}


Therefore, I am here
Click the card below to follow me
↓↓↓

If you like it, please click
Collect
Like
View
+1
❤❤❤