C ++ best coding practice
With the development of computer language, it is more and more easy to compile a program. With some software development tools, the computer will automatically generate a lot of code by dragging the mouse. However, in many cases, this kind of computer capability is abused. We often only consider setting up this program, rather than considering the program's performance, and whether the program is robust enough. The purpose of this course is to introduce some coding experience to make programs more robust and high-performance.
1
, Prefer const and inline to # defineIn C ++ programming, we should try to use const and inline instead of # define, and try to do it without # define. # Define common uses include "defining constants" and "defining macros", but there are many disadvantages. First, the error is not intuitive, which is not conducive to debugging. The definition of define is processed by the preprocessing program. It is completely replaced by text without any type check. In the compiler processing stage, the definition of define has been completely replaced, so that no relevant information can be seen during debug, that is, the step into macro cannot be used for tracking. For example, if we define aspect_ratio as 1.653 by using define, the compiler will not see the name aspect_ratio. If the compiler reports a 1.653 error, there is no way to know where the 1.653 came from. The following statement should be used for real encoding: static const double aspect_ratio = 1.653; second, there is no type information, not type safe. Because it is a replacement at the text level, it is not conducive to program maintenance. Third, the use of define can easily cause pollution. For example, if two header files define aspect_ratio, and a CPP file also contains these two header files, a conflict may occur. Another error is more difficult to query, such as the following code: // In header file def. h # define Apple 1 # define orange 2 # define pineapple 3... // In some CPP file that includes des the def. h Enum colors {white, black, purple, orange}; In. in the H file, orange is defined as a kind of fruit. in the CPP file, the orange is converted into a color, so the compiler will replace the orange here with 2. The compilation may still pass and the program can run, but this becomes a bug, shows odd errors and is difficult to identify. Another example is to define a macro that calculates the numbers of A and B. # define max (A, B) (a)> (B )? (A): (B) int A = 5, B = 0; max (++ a, B); max (++ A, B + 10 ); in the above operation, max (++ a, B); in the statement, a is ++ twice, while Max (++ A, B + 10 ); statement A is added only once, so it may become a bug in program processing, and this bug is also very difficult to find. You can use the following statement for actual encoding: Template <class T> inline const T & MAX (const T & A, const T & B) {return A> B? A: B ;}
2
, Prefer C ++-style castsIn a program, you usually need to convert one type to another. In C ++, you should use the static_cast, const_cast, dynamic_cast, and reinterpret_cast keywords for type conversion. This has the following benefits: first, it is a comment. You can see the above keywords in the Code and immediately know that type conversion is implemented here. Second, in C language, type conversion is usually difficult to search, and through the cast keyword, you can easily find the place where type conversion occurs in the program.
3
, Distinguish between prefix and Postfix forms of increment and Decrement OperatorsGenerally, the prefix (prefix, such as ++ I) and Postfix (suffix, such as I ++) are the same types supported by the operating system or compiler. Because the current compilers are very intelligent, they will automatically optimize the compilation code, the two are the same, there will be no difference in performance. But sometimes there will be different, such as some overloaded operator types. The following describes how to simulate the prefix and Postfix operations. We can find that a temporary variable is generated in the Postfix operation, which takes additional time and overhead. // Prefix form: increment and fetch upint & upint: Operator ++ () {* This + = 1; // increment return * this; // fetch} // Postfix form: Fetch and increment const upint: Operator ++ (INT) {upint oldvalue = * This; // fetch ++ (* This ); // increment return oldvalue; // return what was fetched} generally, you do not need to distinguish whether it is first ++ or later ++, however, when writing a program, we 'd better habitually write it into the form of ++ I. For example, when using iterator in STL, the prefix and Postfix may have a considerable performance difference. Please do not underestimate these details. If you do not pay attention to the specific details when writing a program, you will find that the program performance will be very low. Note that, although postfix can be replaced by prefix in most cases, there is one exception, that is, when the [] OPERATOR exists, for example, gzarray [++ Index] is not equal to gzarray [index ++.
4
, Minimizing compile-time DependenciesSome people prefer to write a program. the H file is included in another. h file, and practice has proved that this is a very bad habit when doing large software, because this will cause many dependency problems, including a lot. another user uses this class, which may not exist in his project. in this way, compilation may fail. In addition, it may be difficult to update a module. Because one. if the H file is included by many modules, if this is modified. when compiling the system, the compiler will find which modules depend on a modified one. h file. all the modules of the H file must be re-compiled. When the project is relatively small, you may not feel the difference, but if it is in a large software system, it may take seven or eight hours to compile the source code. If you do. if the H file is included by many modules. A line of comments is added to the H file. during compilation, the compiler checks which files are modified. all modules of the hfile will be re-compiled, resulting in a huge amount of time and energy burden. The solution to this problem is to make the. h file self-contained, that is, to make it contain as few things as possible. The so-called as few as possible means that if you delete any. h file it contains, it will not work normally. In fact, in many cases, one. h file is not required to include another. h file. You can use the class declaration to solve the dependency problem. Let's take a look at the example below: # include ". H "// Class A # include" B. H "// Class B # include" C. H "// Class C # include" D. H "// Class D # include" E. H "// Class E Class X: Public A, private B {public: E somefunctioncall (E someparameter); Private: D m_dinstance ;}; when Class X is derived from Class A and Class B, you need to know what data X has in the memory. Generally, it is the data of the base class before the memory, the data defined by the derived class is followed by the data defined by this derived class. Therefore, you must know the internal details of Class A and Class B. Otherwise, the compiler cannot arrange the memory. However, you do not need to know the information when processing parameters and return values. The somefunctioncall () defined here only needs to know that E is a class, you do not need to know the details of data in Class E, such as the length. The above code should be rewritten to the following form to reduce the dependency: # include ". H "// Class A # include" B. H "// Class B # include" C. H "// Class C # include" D. H "// Class D Class E; Class X: Public A, private B {public: E somefunctioncall (E someparameter); Private: D m_dinstance ;};
5
, Never treat arrays polymorphicallyDo not use arrays and polymorphism together. Please refer to the following example. Class BST {...}; class balancedbst: Public BST {...}; void printbstarray (ostream & S, const BST array [], int numelements) {for (INT I = 0; I <numelements; ++ I) {S <array [I]; // This assumes an operator <is defined for BST} balancedbst bbstarray [10]; printbstarray (cout, bbstarray, 10 ); the array is a continuous memory space in the memory. How should we locate an element in the array? The process is like this. The compiler can know the length of each data type. If the index of the array is 0, the first element is automatically retrieved. If an index is specified, the compiler automatically calculates the position of the element based on the index and the length of the data type. In the printbstarray () function, although the input parameter is of the balancedbst type, because the type originally defined is BST, the length of the type is still calculated based on BST. Generally, the memory occupied by the instance of the derived class is larger than that occupied by the base class instance. Therefore, this program reports an error during compilation. Remember never put arrays and C ++ polymorphism together.
6
, Prevent exceptions from leaving DestructorsDo not throw exceptions in destructor. There are usually two situations that will lead to the calling of the destructor. One is when the object of this class leaves its domain, or a pointer to this class object in the delete expression, the other is the calling of destructor due to exceptions. If the Destructor is called due to an exception, and an exception is thrown in the destructor, the program will be immediately terminated by the system, or even cannot be released. Therefore, if an exception is thrown in the destructor, it is easy to confuse the cause of the exception, and such software will also make users very angry. Since some other functions are likely to be called in the destructor, it is important to note when writing the destructor. It is very clear whether these functions will throw exceptions, if yes, you must be careful. For example, the following code is used: Session ::~ Session () {logdestruction (this);} For example, The logdestruction () function may throw an exception, so we should adopt the following code form: Session ::~ Session () {try {logdestruction (this);} catch (...) in this case, the program will not be immediately shut down when an error occurs. You can give the user some other options, at least first let him save the current work.
7
, Optimization: Remember the 80-20 ruleThere is a 20-80 rule in the software field, which is actually a very interesting phenomenon. For example, 20% of the Code in a program uses 80% of the resources occupied by the program; 20% of the Code in a program occupies 80% of the total running time; 20% of the Code in a program uses 80% of the memory occupied by the program; 20% of the Code requires 80% of the maintenance power, and so on. This rule can still be promoted, but it cannot be proved to be the result observed in practice. Starting from this rule, we have targeted program optimization. For example, if you want to improve the code running speed, you can know that 20% of the Code occupies 80% of the running time. Therefore, you only need to find the 20% code and optimize it accordingly, the running speed of our program can be greatly improved. Another example is that a function occupies 80% of the running time of the program. If the execution speed of this function is increased by 10 times, the overall performance of the program will be greatly affected. If a function runs only 1% of the total time, the overall performance of the program will not be greatly affected even if the function runs 1000 times faster. Therefore, our basic idea is to find the function that occupies the most running time and optimize it. Even if it only improves a little, the overall performance of the program can be improved a lot. To find out the 20% code, we use profiler, which is actually a tool developed by some companies and can check the memory usage allocated by each module in the program, and the running time of each function. Common profiler include vtune developed by Intel, Visual Studio profiler developed by Microsoft, and devpartner from compuware.