Incidentally, the revised C Standard (C99) allows you to use a const as an array size specification, but the array is treated as a new form of array, called a
One role for the #define directive is still quite useful—the standard idiom for controlling when a header file is compiled:
// blooper.h
#ifndef _BLOOPER_H_
#define _BLOOPER_H_
// code goes here
#endif
For typical symbolic constants, however, you should get into the habit of using const instead of #define. Another good alternative, particularly when you have a set of related integer constants, is to use enum:
enum {LEVEL1 = 1, LEVEL2 = 2, LEVEL3 = 4, LEVEL4 = 8};
Use inline Instead of #define to Define Short Functions
The traditional C way to create the near-equivalent of an inline function is to use a #define macro definition:
#define Cube(X) X*X*X
This leads the preprocessor to do text substitution, with X being replaced by the corresponding argument to Cube():
y = Cube(x); // replaced with y = x*x*x;
y = Cube(x + z++); // replaced with x + z++*x + z++*x + z++;
Because the preprocessor uses text substitution instead of true passing of arguments, using such macros can lead to unexpected and incorrect results. Such errors can be reduced by using lots of parentheses in the macro to ensure the correct order of operations:
#define Cube(X) ((X)*(X)*(X))
Even this, however, doesn’t deal with cases such as using values like z++.
The C++ approach of using the keyword inline to identify inline functions is much more dependable because it uses true argument passing. Furthermore, C++ inline functions can be regular functions or class methods:
class dormant
{
private:
int period;
...
public:
int Period() const { return period; } // automatically inline
...
};
One positive feature of the #define macro is that it is typeless, so it can be used with any type for which the operation makes sense. In C++ you can create inline templates to achieve type-independent functions while retaining argument passing.
In short, you should use C++ inlining instead of C #define macros.
Use Function Prototypes
Actually, you don’t have a choice: Although prototyping is optional in C, it is mandatory in C++. Note that a function that is defined before its first use, such as an inline function, serves as its own prototype.
You should use const in function prototypes and headers when appropriate. In particular, you should use const with pointer parameters and reference parameters representing data that is not to be altered. Not only does this allow the compiler to catch errors that change data, it also makes a function more general. That is, a function with a const pointer or reference can process both const and non-const data, whereas a function that fails to use const with a pointer or reference can process only non-const.
Use Type Casts
One of Stroustrup’s pet peeves about C is its undisciplined type cast operator. True, type casts are often necessary, but the standard type cast is too unrestrictive. For example, consider the following code:
struct Doof
{
double feeb;
double steeb;
char sgif[10];
};
Doof leam;
short * ps = (short *) & leam; // old syntax
int * pi = int * (&leam); // new syntax
Nothing in the C language prevents you from casting a pointer of one type to a pointer to a totally unrelated type.
In a way, the situation is similar to that of the goto statement. The problem with the goto statement was that it was too flexible, leading to twisted code. The solution was to provide more limited, structured versions of goto to handle the most common tasks for which goto was needed. This was the genesis of language elements such as for and while loops and if else statements. Standard C++ provides a similar solution for the problem of the undisciplined type cast—namely, restricted type casts to handle the most common situations requiring type casts. The following are the type cast operators discussed in Chapter 15, “Friends, Exceptions, and More”:
dynamic_cast
static_cast
const_cast
reinterpret_cast
So if you are doing a type cast involving pointers, you should use one of these operators if possible. Doing so both documents the intent of the type cast and provides checking that the type cast is being used as intended.
Become Familiar with C++ Features
If you’ve been using malloc() and free(), you should switch to using new and delete instead. If you’ve been using setjmp() and longjmp() for error handling, you should use try, throw, and catch instead. You should try using the bool type for values representing true and false.
Use the New Header Organization