In this article, I give you programming tips that can improve your C++ code. I will cover use of parentheses, long expressions, modifying objects, and more. By the time I'm done, you will hopefully have learned a few ways to make your code faster as well as easier to understand and maintain.
C++ Fundamentals
Use of parentheses
Consider this expression without parentheses:
and the equivalent with parentheses:
Both expressions perform exactly the same computation, but the meaning of the second one is clearer, since parentheses are used.
Expressions (like the two above) are evaluated by following the rules of precedence. The above expressions are the same. So from a technical point of view, you do not need parentheses. However, including parentheses makes the order of evaluation explicit to the person maintaining your code (even you).
Long expressions (Style)
We often need to write long expressions that will not fit on one line of the screen. For example, the expression
is quite long and will not fit on one line of the screen. A good way to break a long expression into a multiline expression is to use continuation lines. These lines should always begin with an operator and be indented one space. Both of these serve to signal the reader that the line is a continuation of the previous line. So the above example can be written as follows:
As another example, the expression
can be better written as
Speed versus clarity and correctness
One rule of thumb in programming is that 90 percent of a program’s running time is spent on 10 percent of the code. So without some idea of where a program spends most of its time, most changes to a program to speed it up will have little or no effect on the overall running time. While speed is important, clarity (presentation of the code) and correctness are always more important. We should never sacrifice clarity and correctness for speed.
Hot spots are where a program spends most of its time. If speed becomes an issue, a more effective approach is to complete the writing of the program and then order a tool which can be used to identify the hot spots of the program. The hot spots can then be tuned (recoded) to reduce the running time of the program.
Increment and decrement operators (style)
Always use increment and decrement operators to reduce the length of your code. This also portrays you as a mature programmer.
So for example, you should write
instead of
or write
instead of
where i is a C++ variable.
Watch out for operator precedence on long expressions.
Function Usage Basics and Libraries
Controlling the evaluation of assert invocations
The default behavior of an assert invocation is to evaluate assertions. The macro NDEBUG must be undefined for the expression in an assert invocation to be evaluated. Through the use of the following preprocessor statement, the evaluation of subsequent assertions can be turned off.
In some cases, evaluation of assertions can be turned on again through the use of the following preprocessor statement:
This process works because the actions that an assert invocation performs are nested within a conditional evaluation directive that checks whether NDEBUG is defined. It is a good practice to include the definition of NDEBUG at the start of a program that is distributed to users.
Safe input type
Professional software normally extracts its input in character form. This input is then validated and converted to the desired form (e.g. int). This provides a safe way of extracting data. If you do not respect this, you may suffer from the following consequence: if an int is required and the user accidentally types in a non-digit, the program might terminate with an error message that the user does not understand.
Efficiency in Using Constant Reference Parameter
If you pass an object by value to a function, a copy of the object is first made and then passed to the called function. For large objects, copying can add considerable overhead to the program and may also adversely affect the performance of the program. For such objects, you can obtain efficiency and retain safety by passing the object as a constant reference parameter, such as the following:
ReturnValue functionName(const type &objectName)
The const modifier ensures that the called function does not modify the actual object.
Abstract Data Types
Developing object-oriented programs is typically simpler than developing procedure oriented programs. For example, member functions generally have a smaller parameter list than non-member functions because there is no need to pass the data to member functions.
Since well-written member functions ensure that the data members are correctly initialized and updated, other program functions do not need to validate whether they are using proper object values. The access protection provided by encapsulation also ensures that clients use the public interface and not side effects of the library implementation that may continue as the implementation is modified over time.
Rule of minimality
The rule of minimality states that unless a behavior is needed, it should not be part of the Abstract Data Type. A corollary of this rule is the Class Minimality Principle, which states that if a function or operator can be defined such that it is not a member of the class, then do not make it a member. This practice makes a non-member function or operator generally independent of changes to the class’s implementation.
Character strings or string class strings
If you use the string class, you have a more extensive set of capabilities than if you use character strings.
Generic vector extraction
Consider the following:
The syntax template <class T> indicates that what follows is a generic form. In this situation, the form of the function is given. This function template allows different functions to be instantiated based on the particular type of vector used as the actual parameter. For example, if we have the following definitions:
the invocations GetValues(X); GetValues(Y);
cause functions void GetValues(vector<int> &A) { A.resize(0); int Val; while (cin >> Val) { A.push_back(Val); } }
void GetValues(vector<float> &A) { A.resize(0); float Val; while (cin >> Val) { A.push_back(Val); } }
to be defined and invoked.
Accessing multidimensional arrays
Multidimensional arrays are stored in row-major order. Often contiguous sections of memory, called pages, are brought in with the expectation that values near a desired value are more likely to be referenced than values defined elsewhere in memory. Since a page is contiguous memory, it is more likely to contain a complete row than a complete column. So it is generally more efficient to process array elements row-by-row rather than column-by-column. This efficiency comes about in how memory values are brought into the central processing unit, as explained above.
Pointer strength
The ability to access and change the value of an object whose name is not part of the assignment statement is the feature that makes pointer objects so powerful. This capability is most often used for dynamic objects.
Inheritance: Using data member initialization list
There are two ways to initialize data members when implementing the constructor of a class. One way is to invoke the constructor for the data member via the data member initialization list. The other method is to invoke the data member’s mutator from within the body of the constructor. With the second method, the data member’s default constructor is invoked to construct an object with default values, then the mutator is called to overwrite the default values with initial values. Using the first method, the data member is constructed with the appropriate initial values. As you can see the second method is more efficient, because it is done during construction.
Templates and Polymorphism
Automatic subscript checking for vector elements and other list elements
The vector class template provides a member function, which is:
at().
The function expects an index value as its parameter, say i. If the index is valid (exists), a reference to the ith element of the vector is returned. Otherwise, an exception is generated; the program stops, giving an error message that the user does not understand. You can use inheritance to derive a class that overloads the subscript operators so that index checking is automatically performed in conjunction with the subscript operators. To solve this problem, a template class named protectVector is given below.
Assume reuse
If you are defining a class, unless you know it can never serve as a base class for a derive class, give the class a virtual destructor. In this way, all derived destructors will also be virtual and then, whenever an object is deleted, the correct destructor will be invoked, regardless of the context.
All these tips are what I have gathered, based on my experience and the experience of expert programmers. Here's hoping you put them to good use.
- C++ Preprocessor: Always Assert Your Code Is Right (0)2008/04/26
- C++ Preprocessor: The Code in the Middle (0)2008/04/26
- C++ Programming Tips (0)2008/04/26
- C++ Preprocessor: Always Assert Your Code Is Right (0)2007/07/30
- C++ Preprocessor: The Code in the Middle (0)2007/07/30

수안이의 컴퓨터 연구실



Leave your greetings.