inline
Instead of #define
to Define Short FunctionsThe 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.
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
.
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.
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
.
The C++ Standard specifies new names for the header files, as described in Chapter 2, “Setting Out to C++.” If you’ve been using the old-style header files, you should change over to using the new-style names. This is not just a cosmetic change because the new versions sometimes add new features. For example, the ostream
header file provides support for wide-character input and output. It also provides new manipulators such as boolalpha
and fixed
(as described in Chapter 17, “Input, Output, and Files”). These offer a simpler interface than using setf()
or the iomanip
functions for setting many formatting options. If you do use setf()
, you should use ios_base
instead of ios
when specifying constants; that is, you should use ios_base::fixed
instead of ios::fixed
. Also the new header files incorporate namespaces.
Namespaces help organize identifiers used in a program in order to avoid name conflicts. Because the standard library, as implemented with the new header file organization, places names in the std
namespace, using these header files requires that you deal with namespaces.
The examples in this book, for simplicity, most often utilize a using
directive to make all the names from the std
namespace available:
#include <iostream>
#include <string>
#include <vector>
using namespace std; // a using-directive
However, the wholesale exporting of all the names in a namespace, whether needed or not, runs counter to the goals of namespaces.
Somewhat better is placing a using
directive inside a function; this makes the names available just inside that function.
Even better, and the recommended approach, is to use either using
declarations or the scope-resolution operator (::
) to make available just those names a program needs. For example, the following makes cin
, cout
, and endl
available for the rest of the file:
#include <iostream>
using std::cin; // a using-declaration
using std::cout;
using std::endl;
Using the scope-resolution operator, however, makes a name available just in the expression that uses the operator:
cout << std::fixed << x << endl; //using the scope resolution operator
This could get wearisome, but you could collect your common using
declarations in a header file:
// mynames — a header file
using std::cin; // a using-declaration
using std::cout;
using std::endl;
Going a step further, you could collect using
declarations in namespaces:
// mynames — a header file
#include <iostream>
namespace io
{
using std::cin;
using std::cout;
using std::endl;
}
namespace formats
{
using std::fixed;
using std::scientific;
using std:boolalpha;
}
Then a program could include this file and use the namespaces it needs:
#include "mynames"
using namespace io;
Each use of new
should be paired with a use of delete
. This can lead to problems if a function in which new
is used terminates early via an exception being thrown. As discussed in Chapter 15, using an autoptr
object to keep track of an object created by new
automates the activation of delete
. The C++11 additions unique_ptr
and shared_ptr
provide yet better alternatives.
string
ClassThe traditional C-style string suffers from not being a real type. You can store a string in a character array, and you can initialize a character array to a string. But you can’t use the assignment operator to assign a string to a character array; instead, you must remember to use strcpy()
or strncpy()
. You can’t use the relational operators to compare C-style strings; instead, you must remember to use strcmp()
. (And if you forget and use, say, the >
operator, you don’t get a syntax error; instead, the program compares string addresses instead of string contents.)
The string
class (see Chapter 16, “The string
Class and the Standard Template Library,” and Appendix F, “The string
Template Class”), on the other hand, lets you use objects to represent strings. Assignment operators, relational operators, and the addition operator (for concatenation) are all defined. Furthermore, the string
class provides automatic memory management so that you normally don’t have to worry about someone entering a string that either overruns an array or gets truncated before being stored.
The string
class provides many convenience methods. For example, you can append one string
object to another, but you can also append a C-style string or even a char
value to a string
object. For functions that require a C-style string argument, you can use the c_str()
method to return a suitable pointer-to-char
.
Not only does the string
class provide a well-designed set of methods for handling string-related tasks, such as finding substrings, but it also features a design that is compatible with the Standard Template Library (STL) so that you can use STL algorithms with string
objects.
The STL (see Chapter 16 and Appendix G, “The STL methods and Functions”) provides ready-made solutions to many programming needs, so you should use it. For example, instead of declaring an array of double
or of string
objects, you can create a vector<double>
object or a vector<string>
object. The advantages are similar to those of using string
objects instead of C-style strings. Assignment is defined, so you can use the assignment operator to assign one vector
object to another. You can pass a vector
object by reference, and a function receiving such an object can use the size()
method to determine the number of elements in the vector
object. Built-in memory management allows for automatic resizing when you use the pushback()
method to add elements to a vector
object. And of course, several useful class methods and general algorithms are at your service. Or with C++11, if a fixed-size array is a better fit, use array<double>
or array<string>
.
If you need a list, a double-ended queue (or deque), a stack, a regular queue, a set, or a map, you should use the STL, which provides useful container templates. The algorithm library is designed so that you can easily copy the contents of a vector to a list or compare the contents of a set to a vector. This design makes the STL a toolkit that provides basic units that you can assemble as needed.
The extensive algorithm library was designed with efficiency as one of the main design goals, so you can get top-flight results with relatively little programming effort on your part. And the iterator concept used to implement the algorithms means that the algorithms aren’t limited to being used with STL containers. In particular, they can be applied to traditional arrays, too.
3.144.113.163