** abstract: ** this paper combines the author’s work experience and learning experience, some advanced features of C++ language, do a simple introduction; Some common misunderstandings were explained and clarified. On the relatively easy to make mistakes, made a summary; Hope this can enhance everyone’s understanding of C++ language, reduce programming mistakes, improve work efficiency.
A, leads
C++ is a widely used system-level programming language, as well as a high-performance backend standard development language. Although C++ is powerful and flexible, it is an expert language that is easy to learn and difficult to master.
Based on the author’s working experience and learning experience, this paper briefly introduces some advanced features of C++ language. Some common misunderstandings were explained and clarified. On the relatively easy to make mistakes, made a summary; Hope this can enhance everyone’s understanding of C++ language, reduce programming mistakes, improve work efficiency.
Second, the trap
I use global variables in the program, why the process exit inexplicably core drop?
Rule: global variables defined by C++ in different modules (source files) do not guarantee construction order; But ensure that global variables defined in the same module (source file) are constructed in the order defined and destructed in the reverse order defined.
Our program defines the sequence global variables X and Y in a. CPP;
According to the rules: X construct first, Y construct later; When a process stops executing, Y destructs first, then X; But if the destruction of X depends on Y, then the core thing might happen.
Conclusion: If global variables have dependencies, place them in the same source file definition and define them in the correct order to ensure that the dependencies are correct, not in different source files. Singleton dependencies are also a concern for singleton dependencies in the system.
Comparison functions of STD ::sort() are very restrictive and cannot be messed with
I believe that at least 50% of C/C++ programmers who have worked for more than 5 years have been cheated by it. I have heard countless sad stories, such as saint Seiya, Immortal Sword, and other people’s project Love Elimination every day.
If you want to use it, you need to provide your own comparison function or function object, be sure to understand what “strictly weak sort” is, be sure to satisfy the following three characteristics:
- The reflexivity
- asymmetry
- transitivity
Try to sort the index or pointer rather than the object itself, because swapping (copying) the object is more expensive than swapping Pointers or indexes if the object is large.
Note operator short-circuiting
Consider the logic of the game player’s health and mana refresh to the client. The player returns one point of health every 3 seconds, and the player returns one point of blue every 5 seconds. The blue and blue share a protocol to notify the client. That is to say, as long as there is blood or blue, the client will be notified of the new health and magic value.
The player’s heartbeat function () is called in a loop on the main logical thread
void GamePlayer::Heartbeat() { if (GenHP() || GenMP()) { NotifyClientHPMP(); }}Copy the code
Return true if GenHP returns blood, false otherwise; GenHP may not return blood every time it is called, depending on whether the 3-second interval is reached.
Returns true if GenMP is blue, false otherwise; GenMP may not return blood every time it is called, depending on whether the 5 second interval is reached.
If GenHP() returns true, then GenMP() will not be called and may lose the chance to return to blue. You need to modify the program as follows:
void GamePlayer::Heartbeat() { bool hp = GenHP(); bool mp = GenMP(); if (hp || mp) { NotifyClientHPMP(); }}Copy the code
Logic and (&) with logic or (| |) have the same problem, and the if (a & b) if a expression to false, b expression cannot be calculated.
Sometimes, we’ll write if (PTR! = nullptr && PTR ->Do()), which exploits the syntactic feature of short-circuited operators.
Don’t let the cycle stop
for (unsigned int i = 5; i >=0; --i)
{
//...
}
Copy the code
The program went here, WTF? You can’t stop it? Unsigned forever >=0
Fixing this problem is simple, but sometimes these types of errors are not so obvious, and you need to highlight them.
Memory copy Beware of memory overflow
Memcpy, memset is very limited. It can only be used with POD structures. It cannot be used with STL containers or classes with virtual functions.
A class object with a virtual function will have a pointer to the virtual function table, and memcpy will destroy the pointer pointer.
Execute memset/ memCPy for non-POD, and give you four words for free: Seek more happiness from yourself
Pay attention to memory overlap
If SRC and DST overlap, memMOV is used to replace memcpy.
Understand that user Stack space is limited
You cannot define large temporary objects on the stack. In general, the user stack is only a few megabytes (typically 4M, 8M), so objects created on the stack should not be too large.
When formatting a string with sprintf, the type and symbol match exactly
Since the sprintf function is implemented to fetch parameters from the stack by format string, any inconsistency can cause unpredictable errors; /usr/include/inttypes.h defines cross-platform formatting symbols, such as PRId64 for int64_t
Replace the insecure version with the secure version (identified with N) of the C library
For example, strncpy instead of strcpy, snprintf instead of sprintf, strncat instead of strcat, STRNCMP instead of STRCMP, memcpy(DST, SRC, n) should ensure that [DST, DST +n] and [SRC, SRC +n] both have valid virtual memory address Spaces. In multithreaded environments, replace the unsafe version (_r) with a safe version of a system call or library function. Remember that standard C functions such as Strtok and GMtime are not thread-safe.
STL container traversal delete to be careful of iterator invalidation
Vector,list,map,set, etc are written differently:
Int main(int argc, char *argv[]) {//vector to delete STD ::vector v(8); std::generate(v.begin(), v.end(), std::rand); std::cout << "after vector generate... \n"; std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n")); for (auto x = v.begin(); x ! = v.end(); ) { if (*x % 2) x = v.erase(x); else ++x; } std::cout << "after vector erase... \n"; std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n")); / / map traverse delete STD: : map m = {{1, 2}, {8, 4}, {5, 6}, {6, 7}}; for (auto x = m.begin(); x ! = m.end(); ) { if (x->first % 2) m.erase(x++); else ++x; } return 0; }Copy the code
Sometimes traversal delete logic is not so obvious, may in the loop up another function, and the function in a particular situation will delete the current element, in this case, is a long time, procedures are still running well, and when you are laughing with others, all of a sudden crash, it’s embarrassing.
The Saint Seiya project once suffered from this problem, and the basic rule was that game Server crashes once a week plagued the team for nearly a month.
The lower option is to save the element in another container, WaitEraseContainer, and then go through a separate loop to delete the element.
Of course, we recommend deleting at the same time as traversing because it’s more efficient and expert.
Three, performance,
Space for time
Using space to buy time is a good way to improve performance.
Reduce copy & COW
Understand Copy On Write. Copy should be reduced whenever possible, such as through sharing, such as passing parameters and return values in the form of reference Pointers.
Delayed calculation and prediction
For example, the battle power of players on the server side of the game is determined by attributes A and B, that is to say, any change in attributes A and B requires a recalculation of battle power. However, if ModifyPropertyA() and ModifyPropertyB() are both recalculated, it is not really necessary, because it is possible to modify B immediately after modifying property A, and recalculate battle power twice, obviously the result of the first recalculation will be overwritten by the second recalculation.
And in a lot of cases, we might need to push the latest battle value to the client in the heartbeat, so in ModifyPropertyA(),ModifyPropertyB(), we just need to clean up the battle value and delay the calculation so that we don’t need to do the calculation.
Check FightValueDirtyFlag in GetFightValue(). If FightValueDirtyFlag is dirty, recalculate and clear the dirty flag. If not, return the result of the previous calculation.
The idea of predictive computation is similar.
Dispersion calculation
Decentralized calculation is the task scattered, broken, to avoid a large amount of calculation, stuck program.
The hash
Reducing string comparisons and building hashes may cost a little more storage, but it pays off, trust me.
Log restraint
The overhead of logging should not be ignored. To scale, use logging as a debug tool, but release it cleanly.
Why doesn’t the compiler do default initialization for local and member variables
C++ was designed as a system-level programming language because of efficiency. Efficiency is a priority, and a design philosophy of C++ is “don’t pay any extra price for unnecessary operations”. So unlike Java, it does not give default initialization to member and local variables. If initial values are required, it is up to the programmer to ensure that they are assigned.
Conclusion: from a security perspective, should not be used uninitialized variables, define a variable initialization when is a good habit, is a lot of errors because of not properly initialized, c + + 11 support member variable definition directly initialization, member variable initialization in the member initialization list as far as possible, and according to the definition of the initialization sequence.
Understanding the performance overhead of function calls (stack frame creation and destruction, parameter passing, control transfer), performance-sensitive functions consider inline
The X86_64 architecture increases the number of registers to 16, so function calls with few parameters in 64-bit systems will be passed by registers instead of stacks, but stack frame creation, destruction, and control transfer will still affect performance.
Pros and cons of recursion
While recursive functions can simplify programming, they often cause problems with slower execution, so anticipate the depth of recursion and prioritize non-recursive implementations.
Recursive functions should have exit conditions and should not recurse too deeply, otherwise they will burst the stack.
Data structures and containers
Understand all aspects of STD :: Vector and the underlying implementation
- Vector is dynamically expanded, multiplied by the power of 2. To ensure that data is stored in contiguous space, each expansion copies all the original members to the new block. Do not store Pointers to objects in the vector. Expanding the vector will invalidate them. You can save its index instead.
- If a vector needs to be dynamically added or deleted during operation, it is not suitable to store large objects because expansion will cause the construction of all its members to be copied, which consumes a lot of energy. You can save Pointers instead.
- Resize () resets the size; Reserve () is reserved space without changing size(), which can avoid multiple expansion; Clear () does not shrink space. Swap with empty vectors for shrink_to_fit(), STD :: vector.swap (v).
- Understand the difference between at() and operator[] : at() does subscript out of bounds. Operator [] provides array indexer-level access. In the release version, the subscript is not checked. The c++ standard states that operator[] does not provide subscript security checks.
- Recognize and take advantage of the C++ standard, which states that the underlying STD ::vector is implemented as an array.
Common data structures
Array: continuous memory, random access, high performance, good locality, does not support dynamic expansion, most commonly used.
Linked lists: dynamically scalable, extremely fast to detach from inserts, especially with front and back drive Pointers, memory is often discontinuous (which can of course be avoided by allocating from a fixed memory pool), and random access is not supported.
Search: 3 kinds: BST, Hashtable, bSearch based on ordered array. Binary search tree (RBTree), which is ordered from begin to end, worst search speed logN, bad memory discontinuous, nodes have extra space waste; Hashtable, a good hash function is not easy to choose, search the worst degenerate into a linked list, it is difficult to estimate the number of stab, open a large waste of memory, expansion will be stuck, disorderly; Bsearch based on ordered array, good locality, slow INSERT /delete.
5. Best practices
For query structures that are loaded well at startup and do not change during runtime, sorted array can be considered instead of maps, hash tables, etc
Because ordered arrays support binary lookup, they are almost as efficient as maps. For query structures that only need to be built (sorted) once at program startup, ordered arrays may have better memory life neutral (local life neutral) than maps and hashes.
In the process of running, stable query structure (such as configuration table, need to search the configuration table entry by ID, do not add or delete in the process of running), ordered array is a good choice; If it is unstable, the insertion and deletion efficiency of ordered array is worse than that of Map and Hashtable. Therefore, the application of ordered array should be paid attention to.
STD: : map or STD: : unorder_map?
Consider the pros and cons. Map is made of red-black trees, while unorder_map is made of hash tables, which have higher lookup performance than red-black trees. The efficiency of hash tables depends on the hash algorithm, conflict resolution methods (such as hash buckets or zippers), and data distribution. If the load factor is high, the hit ratio will be reduced. To improve the hit ratio, you need to expand and rehash the table, which is slow and equivalent to a delay.
Red-black trees have a better average complexity, so if the data volume is not particularly large, the map can do the job.
Use const actively
Understanding const is not only a syntactic protection mechanism, it also affects how programs compile and run.
Const constants are encoded into machine instructions.
Understand the meanings and differences of the four transitions
Avoid mistakes and use downward transitions as little as possible (can be improved by design)
Static_cast, dynamic_cast,const_cast,reinterpret_cast In short, use casts as little as possible. Casting is C Style. If your C++ code requires strong casts, you need to consider whether the design is wrong.
Understanding byte alignment
Byte alignment makes memory access faster.
Byte alignment is CPU architecture-dependent, and some cpus must access certain types of data at certain address aligned memory locations, otherwise an exception will be raised.
Another effect of byte alignment is to adjust the order in which structure member variables are defined, potentially reducing the size of the structure, which, in some cases, saves memory.
Remember 3 rules and 5 rules, of course C++11 has more copy ctor and op= versions
Customize operator= and copy constructor only if you need to take over. If the default version provided by the compiler works fine, don’t bother.
Composition takes precedence over inheritance, which is the strongest interclass relationship
Typical adapter patterns are class adapters and object adapters, and in general, object adaptation is recommended over inheritance-based class adaptation.
Reduce dependencies and pay attention to isolation
- Minimize dependencies between files and break dependencies with forward declarations.
- Understand pIMPL technology.
- Do not include unnecessary header files, and do not push the inclusion of header files to the user. In short, the inclusion of header files is just fine.
Strict matching
Open handles to close, lock/unlock, new/delete, new[]/delete[], malloc/free to pair, you can use RAII technology to prevent resource leakage, write code to comply with the specification
Valgrind has expectations for the memory usage of the program and needs clean release, so the standard programming can write Valgrind clean code, otherwise the good tool encounters the code not written according to the plan will be useless.
Understand the potential problems of multiple inheritance and use multiple inheritance with caution
Multiple inheritance can cause problems with diamond inheritance. Multiple base classes with the same member variable can cause problems and need to be treated with caution.
Destructors for polymorphic abstract base classes use the virtual keyword
The main reason is that the destructor of the base class gets called correctly.
Delete PTR when the base pointer points to a subclass object. If the destructor is a normal function, the destructor of PTR’s explicit (base) type is called. If the destructor is virtual, the subclass destructor is called, and then the base class destructor is called.
Avoid calling virtual functions in constructors and destructors
In the constructor, the object is not fully constructed, and the call to the virtual function may not bind correctly, as does the destructor.
To get data from the input stream, do a try catch if there is not enough data. Exceptions that are not swallowed are propagated
Reading data from the network data stream and recovering data from the database requires attention.
The protocol should not pass floats. If float is passed, you should understand the concept of NaN and do a good check to avoid malicious propagation
Consider replacing floating-point with an integer, such as five-thousandths (5%%), and save the 5.
Macros are defined in a general way
To bracket each variable, sometimes you need to add do {} while(0) or {} to treat a macro as a statement. Understand that macros are replaced during preprocessing, #undef when not used, and avoid contaminating someone else’s code.
Learn about smart Pointers and pointer misuse
Understand the reference counting method based smart pointer implementation, understand the concept of ownership transfer, understand the differences between shareD_PTR and Unique_PTR and the application scenarios
Consider managing dynamically allocated objects with STD ::shared_ptr.
Pointers are elastic, but don’t misuse them, because they’re elastic because they can change the pointer at run time, so they can be polymorphic, and they can scale dynamically for arrays that don’t have a fixed size, but a lot of times, for arrays of fixed size, we also have new/malloc in init, so we don’t have to, It takes up an extra sizeof(void*) byte and adds a layer of indirect access.
What exactly is size_t? Should I use signed or unsigned integers?
Size_t is a type designed to hold the maximum number of objects that can be held in system memory.
In a 32-bit system, the smallest unit of an object is one byte, so 2 ^ 32 memory, the maximum number of objects that can be held is 4 gb /1 byte, which is exactly what a typedef unsigned int size_t can hold.
Similarly, on 64-bit systems, an unsigned long is 8 bytes, so size_t is the type alias of the unsigned long.
For variables like index and position, is it signed or unsigned? What about properties like money?
In a word: to speak the truth, with the most natural, the most logical type. For example, if the index cannot be size_t negative and the account may owe money, then money is int. Such as:
template <class T> class vector
{
T& operator(size_t index) {}
};
Copy the code
The standard library gives the best demonstration, because if you have symbols, you need to do that
if (index < 0 || index >= max_num) throw out_of_bound();
If (index >= max_num) is an unsigned integer, do you agree?
Int, long is good, short, char you have to be careful to avoid overflow
Integers include int, short, long, long, long, and char. Yes, char is also an integer. Float is real.
In most cases, int, long is good, long is usually equal to the machine word length, can be put directly into registers, and hardware is usually faster to process.
Most of the time, we want to use short and char to reduce the size of the structure. But because of byte alignment, you may not really be able to reduce the size, and one or two bytes of integer bits are too small to accidentally overflow, requiring special attention.
So, unless db, network, etc. are very sensitive to storage size, we need to consider whether to replace int, long with short, char. In other cases, it’s the equivalent of leaving the hallway light on to save electricity, saving little money and risking breaking your leg.
Local variables are unnecessary (unsigned) short, char, etc. The stack is automatically scalable, which is space-saving, dangerous, and slow.
Six, extension,
Understand advanced c++ features
Template and generic programming, union, bitfield, Pointers to members, Placement New, explicit destructor, exception mechanism, nested class, local class, namespace, multiple inheritance, virtual inheritance, volatile, extern “C”, etc
Some advanced features will only be used in certain situations, but you need to accumulate and understand them so that when a need arises, you can use the tools from your knowledge base to deal with it.
Understand the new C++ standard
Focus on new technologies, c++11/14/17, lambda, rvalue references, move semantics, multithreaded libraries, etc
It has been 13 years since the c++98/03 standard was introduced to c++11 standard. In the past 13 years, the idea of programming language has been greatly developed. The new c++11 standard has absorbed many new features of other languages. But the new standard does introduce core syntactic changes such as move semantics, and every CPPer should be aware of the new standard.
OOD design principles are not nonsense
- Six principles of design pattern (1) : Single responsibility principle
- Six principles of design pattern (2) : Richter’s substitution principle
- Six principles of design pattern (3) : dependency inversion principle
- Six principles of design mode (4) : Interface isolation principle
- Six principles of design pattern (5) : Demeter’s Rule
- Six principles of design mode (6) : open and close principle
Be familiar with common design patterns, learn and apply them flexibly, not mechanically
Deification of design pattern and anti-design pattern are not scientific attitudes. Design pattern is a summary of experience in software design, which has certain value. For each design pattern, the GOF book has a dedicated section on its application scenarios and applicability, limitations and drawbacks. It is encouraged to use it if the pros and cons are properly evaluated, but obviously you need to get it right first.
Click to follow, the first time to learn about Huawei cloud fresh technology ~