C
Home Up Search Trademarks how to use

For best results: this site requires that cookies be enabled for proper operation - see Legal Page for more info

 

Select Any of These

C++

LAST UPDATED: 11 March 2009 14:43:18 -0600

 

RE-THROWING A CAUGHT EXCEPTION

A handler that catches an exception can try to fix the problem. Should it fail to do that, it can re-throw the original exception and let another handler fix it. try-blocks can be nested in a hierarchical order, enabling a re-thrown exception from a lower catch-statement to be caught again. A re-throw is indicated by a throw statement without an operand. Example:

 

#include

using namespace std;

bool do_something() { return false; }

 

int main()

{

try //outer try

{

try //inner try

{

if (do_something() == false ) throw "failure";

}//inner try

catch(const char * err) //first handler to cope with the exception

{

cout <<"first chance to handle " <<<endl; chance" second a given <<" <

 

 

 

WHAT IS THE TYPE OF A CLASS TEMPLATE?

A template name by itself is not a type.

 

size_t n = sizeof(vector); //compile time error; template argument is required

Only a template-specialization can be used as a type:

 

unsigned int n = sizeof(vector ); //fine

class myCharVector : public vector //also fine

{/*..*/};




Questions or comments about this tip? Contact our tipster, Danny Kalev at

dannykk@inter.net.il

Danny is a system analyst and software engineer with 10 years of experience, specializing in C++ and object-oriented analysis and design. He is a member of the ANSI C++ standardization committee and a co-author of the Waite Group's C++ How To (Sams Publishing, 1999).

 

 

CONSTRUCTORS' CODE INTO A PRIVATE HELPER FUNCTION

It is very common to define a class that has more than one constructor. For instance, a string class can define one constructor that takes const char * as an argument, another that takes an argument of type size_t to indicate the initial capacity of the string, and a default constructor. Nonetheless, some identical tasks--such as allocating memory from the free store or assigning the size of the allocated storage to a data member--are done in every constructor. Instead of repeating identical operations in each of the constructors, it is better to move the recurring code into a private helper function and have each constructor call this helper function. For example:

 

class string

{

private:

void init(); //called by every constructor

public:

string(const char * s);

explicit string(size_t);

string();

};

The results are shorter compilation time and easier future maintenance.

 




HOW TO TERMINATE A PROGRAM SAFELY

The standard C functions abort() and exit() perform an immediate program termination, regardless of where they are called from. However, it is not a good idea to terminate a C++ program this way, even when a severe error has occurred. The problem is that none of these functions ensures that destructors of local and static objects are called: abort() calls no destructors at all and exit() invokes only the destructors of objects in the scope where it was called from, but not in other scopes. You can imagine the disaster that may result when objects that open files, allocate dynamic memory, etc., are not properly destroyed. A better choice is to throw an exception and catch it somewhere in the program. For example:

 

int main()

{

try

{

//do everything here

}

catch(...)

{

//handle every exception

}

}

C++ guarantees that when an exception is thrown, all destructors of local objects are called before the appropriate catch statement executes. Remember also that exceptions can give the handler a chance to fix the problem and continue, whereas abort() and exit() are unrecoverable.

 




AN EASY AND EFFICIENT WAY TO INITIALIZE LOCAL AUTOMATIC STRUCTS

The easiest, most efficient, and future-proof way to initialize local structs with automatic storage is as follows:

 

struct PlainData

{

char name[20];

long ID;

char phone[15];

//... other fields as many as you like

};

 

int main()

{

PlainData data ={0} ;/*initializes the entire struct

instance with binary zeroes*/

}

This technique is significantly better than manual assignment of values to all the fields of the struct. In addition, the {0} initializer can be applied to every struct type, so even if you make changes in the definition of PlainData, it is guaranteed that its members are all zero-initialized.

 




HOW TO OVERLOAD POSTFIX AND PREFIX OPERATORS

For some objects, the distinction between prefix and postfix overloaded operators is necessary. Postfix operators are declared with a dummy int argument to distinguish them from their prefix counterparts (which take no arguments). For example:

 

claress Date

{

//...

public:

Date& operator++(); //prefix

Date& operator--(); //prefix

Date& operator++(int unused); //postfix

Date& operator--(int unused); //postfix

};

You can use the overloaded operators like this:

 

void f()

{

Date d, d1;

d1 = ++d; /* prefix; first increment d and

then assign to d1 */

d1 = d++; /* postfix; first assign, increment

afterwards */

}

 




WHAT'S IN A NAMESPACE?

A namespace is a scope of declarations. In the following example, the variable n is declared in two distinct namespaces:

 

void f()

{

int n; // 1

}

class A {

int n; // 2

};

The two declarations refer to different variables because they appear in different namespaces. However, the function f() and the class A are declared in the global namespace. This means that f() and A are visible from other scopes. For example, you can call f() from within the class A.

Such global type declarations can be problematic because they might cause name conflicts--the same name can be used to denote different entities, but when these entities are global, the result is name ambiguity. Even if the names are declared in separate source files, the linker will issue an error message on duplicate names. To overcome this problem, namespaces are used. Namespaces enable you to declare entities with identical names in a restricted scope, thereby avoiding name conflicts:

 

namespace MINE

{

void f()

{/*...*/}

}

namespace YOURS

{

void f() /* distinct for f() in namespace MINE */

{/*...*/}

}

int main()

{

/* the two functions can be used without interference */

MINE::f();

YOURS::f();

}

 




ASSIGNMENT OPERATOR SHOULD NOT BE VIRTUAL

Unlike ordinary member functions of a base class, the assignment operator is not inherited. The user can redefine it in a derived class. Otherwise, the compiler will automatically synthesize an assignment operator for the derived class. So there is not much point in declaring the assignment operator virtual.

 




REMEMBER TO UPDATE COMMENTS WHENEVER CODE IS CHANGED

Only one thing is worse than a nondocumented source file: misleading documentation in a source file. For example, a detailed description of a member function that no longer exists, or a documentation of what used to be a default constructor that now appears to be taking three mysterious arguments, can turn software maintenance into a nightmare. So whenever you change your code or re-edit a machine-generated source file, do not forget to update the comments appropriately.

--------------------------------------------------------------------------------

 

 

USE CONST VARIABLES INSTEAD OF #DEFINE MACROS

Using #define to create constants is not recommended. Macros are substituted by the preprocessor, so the compiler usually cannot detect anomalies such as type mismatch (see example). Furthermore, most symbolic debuggers will not allow you to examine a macro's value during debugging.

#define OK 1. /* OOPS! a double instead of an int; undetected */

Instead, try using const int:

const int OK = 1;

In this case, the programmer cannot mistake a float for an int because the compiler will detect the typo. In addition, the value of the const variable can be examined during debugging.

--------------------------------------------------------------------------------

WHAT IS A DEPRECATED FEATURE?

One of the consequences of the evolution and standardization of a programming language is the gradual removal of undesirable, dangerous, or redundant features and constructs. A deprecated feature is one that the current edition of the C++ ANSI/ISO Standard regards as normative, but that is not guaranteed to be part of the Standard in future revisions. By deprecating a feature, the standardization committee expresses the desire to have the feature removed from the language (removing the feature from the language altogether is impractical because existing code will break). By deprecating a feature, the Standardization committee enables the users to remove it gradually from existing code and replace it with the recommended form. Examples of deprecated features are applying postfix ++ to a bool variable, and implicit int declarations:


bool b = false;
b++; /* b becomes true; however, this style
is deprecated */
static n; /* n is implicitly declared as an
int, deprecated */
The recommended forms are


b = true;
static int n;

--------------------------------------------------------------------------------

 

STRING LITERALS ARE CONST

According to the C++ standard, using a non-const pointer to store string literals (a hard-coded quoted text) is deprecated:


char *s = "hello world"; //deprecated
The reason is that the literal "hello world" is a constant. Storing it in a pointer to a non-const char is an error, since it may lead to the following bug:


strcpy(s, "ab"); /* OOPS! attempt to modify
a const object--undefined behavior */
The correct form is


const char *s = "hello world"; //now fine
Subsequently, the compiler can detect the bug:


strcpy(s, "ab");//a compile-time error

--------------------------------------------------------------------------------

 

 

USES OF PREDEFINED MACROS

All C/C++ compilers define the following macros:

 

__DATE__ /* compilation date in the form "Apr 13 1998" */

__TIME__ /* compilation time in the form "10:01:07" */

__FILE__ /* name of the source file being compiled */

__LINE__ /* current line number in the source file */

In addition, a C++ compiler defines the following macro:

 

__cpluplus

An ANSI-conforming C compiler defines the following macro as well:

 

__STDC__

These macros can be useful for debugging and issuing diagnostic messages:

 

if(isSuccessful == false)

{

printf("error occurred; look at line %d

in file %s", __LINE__, __DATE__);

}

 

 

APPLY THE "RESOURCE ACQUISITION IS INITIALIZATION" IDIOM

Many objects of various kinds share a similar characterization: They have to be acquired by means of initialization before their usage, then they can be used, and finally--they have to be released explicitly. Objects such as file, communication socket, database cursor, device context (in GUI applications), and many others have to be opened, attached, initialized, or constructed respectively, before one can use them. When their job is done, they have to be flushed, detached, closed, or released respectively. A common design mistake is to have the user request explicitly for the initialization and release operations to take place.

A much better choice is to move all initialization action into the constructor, and all release actions into the destructor. This technique is called "resource acquisition is initialization." The advantage is a simplified usage protocol. Users can start using the object right after it has been created without bothering whether the object is valid or whether further arbitrary initialization actions have to be done. Furthermore, since the destructor also releases all its resources, users are free from that hassle, too.

 




Remember always to use delete[] to destroy objects that were created using operator new [], as in the following example. Using plain delete instead of delete and vice versa are undefined.

 

#include

using std::string

 

void f()

{

char *pc = new char[100];

string *ps = new string[100];.

delete[] pc;

delete[] ps /* each element's destructor is called */

}

 

 

BETTER ALTERNATIVES TO VOID POINTERS

The type void* serves as a generic data pointer, yet it suffers from the well-known ailments associated with pointers in general: It might be NULL or it might be a dangling pointer. Furthermore, usually you have to cast it back to its original type before using it. This is a dangerous operation. C++ offers higher level mechanisms for genericity that are both safer and more efficient: templates and inheritance. Templates are a better choice for generic algorithms and functions. They are safer since they do not involve pointer manipulations and can be checked at compile time:

 

template void swap ( T f, T s)

{

T temp = f;

f = s;

s = temp;

}

If you need a function or an algorithm that can be applied to a family of types, you can use a common base class from which all other related types are derived:

 

class WindBase

{

public:

virtual void Create()=0;

virtual void Destroy() = 0;

};

class Frame: public WindBase {

//...

};

class View: public WindBase {

//...

};

 

void DestroyWindow(WindBase &anyWindow);

The type void* should be used in very low-level operations.

 

 

BEFORE YOU RESORT TO MACROS...

Macros in C++ are better avoided. They are unsafe, hard to debug, and can easily bloat your executable file. C++ offers significantly better alternatives to them: inline functions and const variables.

An inline function is safer than a macro since it provides type checking. Furthermore, you can overload an inline function and step into its definition during debugging.

Macro constants should be replaced with const variables or enum types. Both allow safe type checking and they are easier to debug.

In general, macros in C++ should be used in conditional compilation only.

 

 




SIZEOF OR STRLEN()?

What's wrong with the following code excerpt?

 

const char name[] = "john doe"; /* 9 characters, null char

implicitly added by compiler */

 

size_t namesz = strlen(name); /* namesz == 8 */

Nothing, in fact. Code such as this does exist and is perfectly legal. However, it's inefficient and error prone. The function strlen() computes the length of the string at runtime. However, the string length can be computed at compile-time in this case, by using the sizeof operator. Furthermore, there's a potential bug that may result from using strlen(). strlen() does not count the terminating null character.

To get the string size correctly, you have to increment the returned value of strlen() by 1. Sizeof, however, returns the correct number of characters including the terminating null:

 

size_t sz = sizeof(name); //sz equals 9

Still, strlen() is sometimes unavoidable. A function that takes a char[] argument is implicitly transformed by the compiler into a function that takes a pointer to char. Applying the sizeof operator inside a function to its char[] argument is most likely a bug, since it returns the size of a pointer, rather than the array size. Therefore, you should use the operator sizeof to compute an array's size only in the scope where the array is declared; otherwise, strlen() should be used.

 

 




UNDERSTANDING THE DIFFERENCE BETWEEN "UNDEFINED BEHAVIOR" AND "UNSPECIFIED BEHAVIOR"

The terms "undefined behavior" and "unspecified behavior" are not interchangeable. Undefined behavior indicates that an implementation may behave unpredictably when a program reaches a certain state, which, almost without exception, is a result of a bug. Undefined behavior can be manifested as a runtime crash, unstable and unreliable program state, or--in rare cases--it may even pass unnoticed. Examples of undefined behaviors are an attempt to write to a buffer past its boundary; dereferencing a dangling pointer, or deleting a non-NULL pointer to an object more than once.

Unspecified behavior, on the other hand, is a consistent and documented behavior that a certain implementation applies in cases that are left intentionally unspecified by the C++ Standard. Examples of unspecified behaviors are the size of an int; whether a char is unsigned or signed by default; or the size of a pointer. It is guaranteed that the behavior of a certain implementation is consistent. For instance, all variables of type int have the same size on the same machine. There is no guarantee, however, that on another machine an int will occupy the same size.

Unspecified behavior is usually something you shouldn't worry about, unless your software is required to be portable. Conversely, undefined behavior is always undesirable and should never occur.

 




A PURE VIRTUAL DESTRUCTOR MUST BE DEFINED

Unlike ordinary member functions, a virtual destructor is not overridden when redefined in a derived class. Rather, it is extended: the destructor of the derived class automatically invokes the destructor of its base class. Consequently, when you try to declare a pure virtual destructor, you will encounter either a compilation error, or worse--a runtime crash. But there's no need to despair. You can enjoy both worlds by declaring a pure virtual destructor without a risk. First, the abstract class should contain a declaration (without a definition) of a pure virtual destructor:

 

class Interface

{

public:

virtual ~Interface() = 0; /* pure virtual destructor

declaration */

//..other stuff

};

Then, outside the class body, the pure virtual destructor has to be defined like this:

 

Interface::~Interface()

{} /* definition of a pure virtual destructor; should

always be empty */

 

 




NEVER CHANGE DEFAULT ARGUMENTS OF A VIRTUAL FUNCTION IN A DERIVED CLASS

You should pay attention when using default arguments in virtual functions. The default values in the overriding function have to be identical to the default values of the corresponding base class member. Otherwise, unexpected results may occur:

 

class Base

{

public:

virtual void say (int n = 0) const { cout <<N;} class="" p- Derived; *p = new Base }; * value default changing bad, <<n;} cout { const n = 5) (int say void public: public : Derived say(); //output 0 and not 5!

Why is this? Unlike virtual functions, which are resolved at runtime, default arguments are resolved at compiled time. In the example above, the static type of p is "pointer to Base." The compiler therefore supplies 0 as a default argument in the function call. However, the dynamic type of p is "pointer to Derived," and Derived takes 5 as a default argument.

Since a base class and a derived class can be compiled separately, compilers usually cannot detect such inconsistencies. Therefore, it is the programmer's responsibility to make sure that the default values of a virtual function are identical in all levels of derivation.

 

 




UNDERSTANDING THE DIFFERENCE BETWEEN INTERNAL LINKAGE AND EXTERNAL LINKAGE

An identifier that can be referred to in a translation unit (source file) other than the one in which it was defined is said to have external linkage. Examples of names with external linkage are global variables, external functions, class declarations, and const variables that are explicitly declared as extern.

Conversely, an identifier that can be used only from within the translation unit in which it was declared is said to have internal linkage. Examples of these are const variables and static functions:

 

static void f() /* f is visible only within this

translation unit */

{

}

void g() /* g has external linkage */

{}

 

const int k =0; /* k has static linkage */

extern const m = 1; /* m has external linkage */

 

 




WHAT IS A FULLY QUALIFIED NAME?

A unique name of an identifier consists of its namespaces, each followed by scope resolution operator, then its class name, and finally, the variable itself. Since both namespaces and classes can be nested, the resulting name can be rather long, yet it ensures unique identification:

 

/* fully qualified name of npos is a constant member of

string; string itself is in namespace std */

 

size_t max = std::string::npos;

 

int * p = ::new int; /* fully qualified name of global

operator new */

Such a name is called "a fully qualified name."

 

 




WHAT IS A TRIVIAL CONSTRUCTOR?

Conceptually, compilers synthesize a default constructor for every class or struct, unless a constructor was already defined by the user. However, in certain conditions (a simple data struct, for example), a synthesized constructor is completely redundant. Compilers avoid synthesizing a default constructor for a class/struct when all the following conditions hold true:

1. The class/struct does not have any virtual base classes
2. It does not define or inherit any virtual member functions
3. It does not contain a member object that has a nontrivial constructor either

By eliminating unnecessary constructor generation, compilers can produce code that is as efficient in terms of size and speed as a C compiler would.

 

 




OBTAINING THE ANSI/ISO C++ STANDARD

The ANSI/ISO C++ Standard is now available for downloading from the NCITS (National Committee for Information Technology Standards) Web site at

http://www.cssinfo.com/ncitsgate.html

The downloadable PDF format costs $18. Note that the standard is not written in plain English, and it is definitely not a tutorial of the language. However, if you wish to become a "language lawyer," you can start right here.

 

 




AVOID USING LONGJMP() IN C++

Using longjmp() in C++ is dangerous, because longjmp() jumps out of a function without properly unwinding the stack. Consequently, destructors of local objects are not invoked. As a rule, long jumps should be used in pure C exclusively. In C++ code, you should use standard exception handling instead.

 




USE THE EMPTY EXCEPTION SPECIFICATION JUDICIOUSLY

Some programmers tend to add an empty exception specification to the constructor destructor, assignment operator, and copy constructor. For example:

 

class C {

public:

C() throw();

~C() throw();

C(const C&) throw();

C& operator= (const C&); throw();

};

On some implementations, this practice can improve performance, because the compiler is free not to generate extra "scaffolding" code to support exception handling. However, on other implementations, the addition of empty exception specifications achieves the opposite result. Therefore, it is advisable to check the compiler's documentation first to make sure that such empty exception specifications do not result in a performance penalty. If your code is used on various platforms, avoid empty exception specifications.

 

 




CONST AND REFERENCE MEMBERS MUST BE INITIALIZED BY A MEMBER-INITIALIZATION LIST

Nonstatic const data members and reference data members of a class must be explicitly initialized by a member-initialization list, as in the following example:

 

class Allocator

{

private:

const int chunk_size;

string & path;

public:

//constructor with a mem-initialization list

Allocator(int sz, string& p) : chunk_size(sz), path (p) {}

};

 




INFORMATION ABOUT THE NEW STANDARD PROPOSAL FOR C9X

The ISO standard of the C programming language was approved a decade ago. C9X is an effort to produce a new, improved standard for the C programming language. The following Web site contains information about the new standard proposal for C9X, with a list of changes from C as it is defined by the ISO standard:

http://lglwww.epfl.ch/~wolf/c/c9x_changes.html

It's advisable not to use the new C9X keywords as identifier names in your C++ programs to avoid possible maintenance difficulties in the future.

 




WHEN A USER-DEFINED COPY CONSTRUCTOR AND ASSIGNMENT OPERATOR SEEM UNAVOIDABLE...

In general, classes that possess pointers, files, and other resources need a user-defined assignment operator and copy constructor to avoid aliasing. For example:

 

class Person {

private:

int age;

char * name;

public: /* the following three member functions

must be defined by the class implementer */

Person & operator= (const Person & other); /*

user-defined assignment operator */

Person (const Person& other); /* copy constructor */

};

Seemingly, there's no escape from explicitly defining a copy constructor and assignment operator for class Person; otherwise, the compiler-generated copy constructor and assignment operator will result in aliasing.

However, the aliasing problem in this case results from the reliance on low-level language constructs (a bare pointer). Had a std::string object been used instead of a pointer to char, class Person wouldn't need a user-written copy constructor and assignment operator.

As a rule, when a class needs a user-defined assignment operator and copy constructor, it may indicate a design flaw rather than an unavoidable necessity.

 




OVERLOADED OPERATORS CANNOT TAKE DEFAULT ARGUMENTS

Overloaded operators cannot declare a parameter with a default value (the function call operator, (), is the only exception to this rule). For example:

 

class Date

{

private:

int day, month, year;

public:

Date & operator += (const Date & d = Date() ); //error

};

This rule captures the behavior of built-in operators, which never have default operands, either.

 




DIFFERENCES IN THE UNDERLYING REPRESENTATION OF NULL BETWEEN C AND C++

C and C++ implementations define the symbol NULL differently:

#define NULL 0; /* A typical definition of NULL in C++ */
#define NULL ((void*)0) /* C implementations usually define NULL this way */

Why is this? Pointers in C++ are strongly typed, unlike pointers in C. Thus, void* cannot be implicitly converted to any other pointer type without an explicit cast. If C++ retained C's convention, a C++ statement such as

char * p = NULL;

would be expanded into something like

char * p = (void*) 0; /* compile time error: incompatible pointer types */

Therefore, C++ implementations use the literal 0 rather than a void pointer as the underlying representation of NULL.

 




MARKING LINE CONTINUATION

C and C++ statements can be broken into more than a single line. However, you cannot break literal strings such as printf() format strings, macros, or literals. When you need a long string, you can use the continuation marker \ (backslash) before a line break:

 

#define MESSAGE "a very long message that cannot \

fit into a single line. It may be broken more than once \

like this"

 

printf ("%10.10ld \t %10.10ld \t %s\

%f", w, x, y, z );

The preprocessor scans the source file before compilation and concatenates broken lines that are delimited by a continuation marker. Thus, the compiler sees such lines as if they were typed without a break. Note that the line break should appear immediately after the backslash.

 




WHEN ARE POINTERS EQUAL?

Pointers to objects or functions of the same type are equal in one of the following three conditions:

If and only if both pointers are NULL:

 

int *p1 = NULL, p2 = NULL;

bool equal = (p1==p2); //true

Or if they point to the same object:

 

char c;

char * pc1 = &c;

char * pc2 = &c;

equal = (pc1 == pc2); // true

Finally, pointers are equal if they point one position past the end of the same array. For example:

 

int iarr[2];

int * p1 = iarr+2; /* p1 points one position past iarr */

int * p2 = iarr+2; /* p2 and p1 are equal */

 




INITIALIZING A UNION

You can initialize a union. Yet, unlike struct initialization, the initialization list of a union must contain a single initializer, which refers to the first member of the union. For example:

 

union Key {

int num_key;

void *ptr_key;

char name_key[10];

};

Key a_key = {5}; /*first member of Key is of type int; all other bytes are initialized to binary zeroes*/

 




ALWAYS INITIALIZE POINTERS

An uninitialized pointer has an indeterminate value. It's almost impossible to test subsequently whether such a pointer is valid, especially if it is passed as an argument to a function, which in turn, can only verify that it is not NULL. For example:


void func(char *p );

int main()
{
char * p; //dangerous; an uninitialized pointer
//...many lines of code
if (p) /* erroneously assuming that a non-NULL
value indicates a valid address*/
{
func(p); /* func has no way of knowing whether
p has a valid address; disastrous */
}
}
Even if your compiler does initialize pointers automatically, your code will be more readable and portable if you use explicit initializations.

--------------------------------------------------------------------------------

 

USING EXPLICIT INTEGER AFFIXES

To help the compiler figure out the correct type of a hard-coded integer, you can use explicit affixes. The letter L affixed to a number indicates a long integer. In addition, the letter U indicates an unsigned type. For example:


#define MAX 1000L /* long rather than an int */
#define MAX 1000U /* an unsigned int */
These affixes can be combined:


#define MAX 1000UL // unsigned long
Note that these affixes aren't case sensitive, so you can use lowercase and uppercase affixes interchangeably:


#define MAX 1000ul // also unsigned long

--------------------------------------------------------------------------------

 

IN-CLASS INITIALIZATION OF CONST STATIC DATA MEMBERS

The C++ Standard now allows initialization of const static data members of an integral type inside their class, as in the following example:


class Buff
{
private:
static const int MAX = 512;
static const char flag = 'a';
static const std::string msg; /*
non-integral type; must be defined outside the class body */
public:
//..
};
const std::string Buff::msg = "hello";
The initialization inside the class body also defines the data member, so it shouldn't be defined outside the class, as opposed to const static data members of nonintegral types, which have to be defined outside the class body.

--------------------------------------------------------------------------------

 

ENSURING PROPER DESTRUCTION OF LOCAL OBJECTS IN THE CASE OF AN UNCAUGHT EXCEPTION

Whether the destructors of local objects are invoked when an uncaught exception occurs is implementation dependent. To ensure that destructors of local objects are invoked in this case, you can add a catch-all statement in main(), as follows:


int main()
{
try
{
//...
}
catch(std::exception& exc) // handle
expected exceptions
{
//...
}
catch(...) /* ensure proper cleanup
in the case of an uncaught exception */
{
}
}

--------------------------------------------------------------------------------

 

THE DEFAULT ACCESS SPECIFIER OF THE SPECIAL MEMBER FUNCTIONS

The default constructor, copy constructor, assignment operator, and destructor are called special member functions. The implementation implicitly declares these member functions for a class if the programmer did not explicitly declare them. The default access specifier for these implicitly declared functions is always public.

--------------------------------------------------------------------------------

 

THE STANDARD C++ STREAMS

C++ provides four standard I/O streams that are automatically instantiated before a program's outset:


cin // standard input stream of char
cout // standard output stream of char
cerr // standard unbuffered output stream for error messages
clog // standard output stream for error messages
The difference between cerr and clog is that cerr is unbuffered, whereas clog is buffered. In addition, each of these four streams has a corresponding wide character version:


wcin
wcout
wcerr
wclog

--------------------------------------------------------------------------------

 

MULTITHREADING IN C++

Standard C++ does not address directly the issue of multithreading and thread safety. Rather, it is an implementation-dependent feature. Therefore, vendors may provide multithreading support for a specific environment, but they are not required to do so. It is important to note, however, that nothing in the Standard Library or the C++ language itself disallows multithreading and thread safety. Thus, in a multithreaded environment, STL containers are implemented in a thread-safe manner, whereas on a single-threaded platform (such as DOS), they may be implemented in a non-thread-safe manner.

--------------------------------------------------------------------------------

 

SUPPRESSING TRAILING ZEROS ON OUTPUT

The format string "%f" in printf() displays trailing zeros:


float num = 1.33;
printf( "%f", num); // output: 1.330000
To suppress the trailing zeros, you can use the "%g" format instead:


printf( "%g", num); //output: 1.33

--------------------------------------------------------------------------------

 

AN ADDITIONAL ADVANTAGE OF DECLARING AN OBJECT AS CONST

By declaring a variable that doesn't change throughout a program's execution as const, you make your code safer and more readable. However, there's an additional advantage--efficiency. An optimizing compiler can take advantage of a const declaration and store the const object in a machine register instead of ordinary memory. Such optimization can sometimes boost performance significantly.

--------------------------------------------------------------------------------

 

OVERLOADING OPERATORS AS MEMBER AND NONMEMBER FUNCTIONS

Most of the overloaded operators can be declared either as nonstatic class members or as nonmember functions. In the following example, operator == is overloaded as a nonstatic class member:


class Date
{
//...
public:
bool operator == (const Date & d ); // 1: member
function
};
Alternatively, you can declare it as a friend function like this:


bool operator ==( const Date & d1, const Date& d2);
//extern function
class Date
{
public:
friend bool operator ==( const Date & d1, const Date& d2);
};
Nonetheless, the operators [], (), =, and -> can only be declared as nonstatic member functions. This ensures that their first operand is an l-value.

--------------------------------------------------------------------------------

 

DATA POINTERS AND FUNCTION POINTERS

C and C++ make a clear-cut distinction between two types of pointers--data pointers and function pointers. A function pointer embodies several constituents such as the list of arguments, a return-to address, and the machine instructions. A data pointer, on the other hand, merely holds the address of the first byte of a variable. The substantial difference between the two led the C standardization committee to prohibit the use of void* to represent function pointers and vice versa. In C++, this restriction was relaxed, yet the results of coercing a function pointer to a void* are implementation dependent. The opposite--that is, converting of data pointers to function pointers--yields undefined behavior and should be avoided.

--------------------------------------------------------------------------------

 

DECLARE NON-MODIFYING MEMBER FUNCTIONS AS CONST

Class member functions are divided into two categories: modifying member functions, which are allowed to change the object's state, and non-modifying member functions, which can observe the object's state but cannot change it (an object's state consists of the values of its nonstatic data members). A non-modifying member function should be declared as const, thereby promising that it does not change the state of its object. For example:


class Person
{
private:
int age;
public:
/* a modifying member function */
void setAge (int Age);
/* a non-modifying function */
int getAge () const { return age; }
};
Remember to declare every non-modifying member function as const. There are two advantages to this practice: First, it renders your code more readable and self-documenting. Second, the compiler can detect errors such as the following:


int getAge () const { return age = 0; } /* compilation
error; attempt to change the state of the object */

--------------------------------------------------------------------------------

 

USE ABSTRACT CLASSES TO SPECIFY INTERFACES

An abstract class has at least one pure virtual member function. A pure virtual member function is indicated by the =0 suffix; it is only a declaration, which is left unimplemented in its class. For example:


class File { /* an abstract class; serves as interface */
public:
int virtual open() = 0; // pure virtual
int virtual close() = 0; // ditto
};
An abstract class is not an ordinary class; rather, it serves as a means of enforcing a common interface in classes that are derived from it. A derived class may implement the pure virtual functions of its abstract base class. In that case, you can create objects of the derived class:


class diskFile: public File {
private:
string filename;
public:
/* open() and close() are implemented in this class */
int open() { /*...*/ }
int close (){ /*...*/ }
};

--------------------------------------------------------------------------------

 

WHAT IS KOENIG LOOKUP?

Andrew Koenig devised an algorithm for resolving namespace members' lookup. This algorithm, also called argument dependent lookup, is used in all standard-compliant compilers to handle cases such as the following:


namespace MINE
{
class C {};
void func(C);
}

MINE::C c; /* global object of type MINE::C */

int main()
{
func( c ); // OK, MINE::f called
}
Neither a using declaration nor a using directive exists in the program. Still, the compiler did the right thing--it correctly identified the unqualified name func as the function declared in namespace MINE by applying Koenig lookup.

In essence, Koenig lookup instructs the compiler to look not just at the usual places, such as the local scope, but also at the namespace that contains the argument's type. The compiler knows that the object c, which is the argument of the function func(), belongs to namespace MINE. Consequently, it looks at namespace MINE to locate the declaration of func(), "guessing" the programmer's intent.

Without Koenig lookup, namespaces would impose an unacceptable tedium on the programmer, who would have to either repeatedly specify the fully qualified names or use numerous using declarations.

--------------------------------------------------------------------------------

 

HOW TO DECIDE BETWEEN IS-A OR HAS-A RELATIONSHIP

When designing a class hierarchy, you often have to decide between inheritance (is-a) or containment (has-a) relationship. Assume that you are designing a Radio class and that you already have the following classes implemented for you in some library: Dial and ElectricAppliance. It is obvious that your Radio should be derived from ElectricAppliance. However, it is not so obvious that Radio should also be derived from Dial.

How to decide? Check whether there is always a 1:1 relation between the two: Do all radios have one and only one dial? No, a radio can have no dials at all--a transmitter/receiver adjusted to a fixed frequency, for example. Furthermore, it may have more than one dial--FM and AM dials at once. Hence, your Radio class should be designed to have Dial object(s) rather than being derived from Dial. Note that the relation between Radio and ElectricAppliance is always 1:1 and corroborates the decision to derive Radio from ElectricAppliance.

--------------------------------------------------------------------------------

 

BE CAUTIOUS WITH UNSIGNED INTEGERS

Unsigned integers are sometimes unavoidable when dealing with raw binary data and in other low-level applications such as encryption, digital communication, and compression. Nevertheless, in other mainstream programming tasks, unsigned is to be avoided in general. A common--but wrong--practice is to use codes as unsigned integers. For example, a Geographic Information System can have a lookup function that retrieves a city code by its name:


typedef unsigned int zip;
zip translate(const string& cityname);
The function translate() returns a city code from a lookup table. For new cities that do not yet have a code in the lookup table, zero is returned. Under normal usage conditions, this seems reasonable. But how do you handle a validation exception such as an illegal string? Negative values can be used to indicate error conditions; however, translate() returns only positive values. This is a classic case of how the misuse of unsigned integers can limit possible future extensions.

Another problem with using unsigned integers is that they are automatically converted to signed, and vice versa, with a possible loss of their original value. For example:


void translate(unsigned int code, string& descr);
const int ERROR = -1;
//...additional codes
const int MEM_ERR = -100;
int mem_initialize ();

int main()
{
char err_description[30];
if ( mem_initialize() == ERROR)
translate(MEM_ERR, /*unexpected results*/
err_description);
}
For these reasons, a signed integral type is usually a more portable and safe choice. There are two main issues of concern here: eliminating possible bugs that can result from mixing signed and unsigned values, and the future maintenance problems that can arise due to the use of unsigned. Picking unsigned just because the language happens to have this feature is, at best, limiting. Therefore, avoid using unsigned unless you have a good reason for doing so.

--------------------------------------------------------------------------------

 

WHEN IS VIRTUAL INHERITANCE NEEDED?

Multiple inheritance is a powerful feature. However, it can also lead to a problem known as the DDD, or "Dreadful Diamond of Derivation," as in the following example:


class ElectricAppliance{
private:
int voltage,
public:
int getVoltage () const { return voltage; }
};

class Radio : public ElectricAppliance {/*...*/};
class Tape : public ElectricAppliance {/*...*/};

class RadioTape: public Radio, public Tape {/*..*/};
int main()
{
RadioTape rt;
int voltage = rt.getVoltage(); /* Error - ambiguous call.
Two copies getVoltage() exist in rt */
}
RadioTape is derived simultaneously from two base classes; each of these has its own copy of the methods and data members of ElectricAppliance. Consequently, rt has two ElectricAppliance subobjects. In cases like this, where reduplication of data and methods from a common base class is undesirable, virtual inheritance should be used:


class Radio : virtual public ElectricAppliance {/*..*/};
class Tape : virtual public ElectricAppliance {/*..*/};
class RadioTape: public Radio, public Tape {/*..*/}
Now class RadioTape contains a single shared instance of ElectricAppliance, so there are no ambiguities:


int voltage = rt.getVoltage(); //OK

--------------------------------------------------------------------------------

 

PREFER MEMBER INITIALIZATION LIST TO ASSIGNMENT WHEN INITIALIZING MEMBER OBJECTS

You should initialize embedded objects of a class by a member initialization list instead of assigning their values inside the class constructor. For example:


class Person
{
private:
string name; //embedded object
public:
Person(string n) : name(n) {}
};
Using plain assignment within the constructor body, as in


class Person {
private:
string name;
public:
Person(string n)
{name = n; } /*plain assignment; inefficient*/
};
is significantly less efficient, since each embedded object is constructed twice. In contrast, a member-initialization list ensures that the constructor of each embedded object is invoked only once.

--------------------------------------------------------------------------------

 

THE USES OF THE OPERATOR CONST_CAST

The const_cast operator is one of the four typecast operators that were added to C++ to replace the C-style cast. The const_cast operator can cast away only the const/volatile qualities of its operand. The expression const_cast (Expr) removes the const and volatile qualifiers of Expr and converts it to type T. T must be the same type of Expr, except for the missing const or volatile attributes. For example:


#include
using namespace std;
void print(char *p) /* parameter should have been declared
as const; alas */
{
cout < (msg); /*remove constness*/
print(p);
}
The const_cast operator can also perform the opposite operation, that is, convert an object to a const or volatile one:


void read(const int * p);
int *p = new int;
read( const_cast (p) ); /* explicit */
Note that the removal of the const qualifier of an object does not guarantee that its value can be modified; it only guarantees that the object can be used in a context that requires a non-const object.

 

 

USE STATIC_CAST INSTEAD OF C-STYLE CAST TO PERFORM SAFE AND RELATIVELY SAFE CASTS

One of the four typecast operators that were added to C++ to replace the C-style cast is static_cast. The expression static_cast (Expr) casts the operand Expr to the type T at compile time. One of the uses of static_cast is to explicitly indicate a type conversion that is otherwise performed implicitly by the compiler. For example:


class Base{};
class Derived : public Base {};
Derived * pd = new Derived;
Base * pb = static_cast (pd); /*explicit*/
Although a pointer to a derived object is automatically converted to a pointer of its base, the use of an explicit cast makes the programmer's intent more visible. The static_cast operator can also cast an integral value to an enum:


void func()
{
enum status {good, bad};
int num = 0;
status s = static_cast (num);
}
Note that static_cast may not be used to remove the const or volatile qualifiers of an object; for that purpose, you should use the const_cast operator.

--------------------------------------------------------------------------------

 

USE OPERATOR REINTERPRET_CAST FOR UNSAFE CONVERSIONS

The operator reinterpret_cast is used for low-level and unsafe type conversions, such as converting two pointers of nonrelated types. For example


void mem_probe()
{
long n = 1000000L; long *pl = &n
unsigned char * pc = reinterpret_cast
(pl);
printf("%d %d %d %d", pc[0], pc[1], pc[2], pc[3]);
/*memory dump*/
}
The operator reinterpret_cast can also be used to cast integers to pointers, and vice versa:


void *pv = reinterpret_cast (0x00fffd);
int ptr = reinterpret_cast (pv);
The operator reinterpret_cast returns a low-level presentation of the bit pattern of its operand. The use of reinterpret_cast can be dangerous and highly nonportable--use it sparingly.

Users might find the proliferation of new cast operators somewhat confusing. In particular, the choice between static_cast and reinterpret_cast might not seem immediately clear. How to choose? As a rule, static_cast is your first choice. If the compiler refuses to accept it, use reinterpret_cast instead.

--------------------------------------------------------------------------------

 

THE SCOPE OF A LOCAL LOOP COUNTER

A variable that is declared in a for-statement is inaccessible outside that for-statement. For example:


void f()
{
for (int i = 0; i <10; i++) /* i declared and
initialized inside a for-statement */
{
cout << } * scope in not i error, compilation n="i;" int <
In earlier stages of C++, a local variable declared this way remained accessible in its enclosing block, but this was a source
for bugs and name hiding. The C++ Standard was revised to fix this loophole, so to speak, and as a result, the scope of such a
variable is limited to the for-loop.






--------------------------------------------------------------------------------



HOW TO INVOKE A DESTRUCTOR EXPLICITLY

Under some rare circumstances, you have to invoke an object's destructor explicitly--for example, when using the placement new operator. You can invoke an object's destructor in the following manner:


void f(char * buff)
{
string p = new (buf) string("hi"); /*placement*/
//...use p
p-> string::~string(); /*explicit destructor invocation */
}
You can also invoke an object's destructor from within a member function:


void C::destroy()
{
this-> C::~C();
}

--------------------------------------------------------------------------------

 

COPY CONSTRUCTOR AND ASSIGNMENT OPERATOR GO TOGETHER

If you do not define a copy constructor and operator =, the compiler will create them automatically for you. However, there are cases when you have to define them explicitly. In such cases, remember to define BOTH of them and not just one. Otherwise, the compiler will create the missing one, but it might not work as expected.

--------------------------------------------------------------------------------

 

CONSTRUCTORS AND DESTRUCTORS SHOULD BE MINIMAL

When you are designing a class, remember that it might serve as a base class for other classes. It can also be used as a member object of a different class. Compared to ordinary member functions, which can be overridden or simply not called, the base class constructor and destructor are automatically invoked. Therefore, it's not a good idea to force users of a derived and embedded object to pay for what they do not need but are forced to accept. In other words, constructors and destructors should contain nothing but the minimal functionality that is needed to construct an object and destroy it.

A concrete example can demonstrate that: A string class that supports serialization should not open/create a file in its constructor. Such operations need to be left to a dedicated member function. When you derive another class from String--such as ShortString, which holds a fixed-length string--the constructor of the derived class is not forced to perform superfluous file I/O that is imposed by the constructor of its base class.

--------------------------------------------------------------------------------

 

USE ENUM TYPES TO CREATE A CLOSED SET OF VALUES

Creating a closed set of values with #define macros, as in the following example, is not recommended:


#define JAN 1
//...
#define DEC 12
Instead, use enum types for this purpose, because they offer value abstraction and type-safety. Enums are strongly typed; therefore, only values from their enumerator list can be assigned to them:


enum Months{
Jan,
//...
Dec
};
enum Days{
Mon,
//...
Sun
};
void f(){
Days day = Mon;
day = Jan; //error, type mismatch
}
Enums are distinct types, so they can be used to overload functions:


void f(Months month);
void f(Days day);
In terms of performance, enum types are efficient because the compiler automatically converts them to an unspecified integral type. The compiler can optimize memory usage by compacting the enumerator list values in units that are smaller or larger than int. Furthermore, they can be stored in the machine's registers to create more efficient code.

--------------------------------------------------------------------------------

 

THE DIFFERENCE BETWEEN A USING DECLARATION AND A USING DIRECTIVE

A namespace is a scope in which declarations are grouped together (these are called namespace members). To refer to any of these members from another scope, a fully qualified name is required. However, repeating the fully qualified names is tedious and less readable. Instead, a using declaration or a using directive can be used.

A using declaration consists of the keyword "using" followed by a namespace member. It instructs the compiler to locate every occurrence of a certain namespace member (a type, operator, function, and so on), as if its full-qualified name were supplied:


#include /* vector is declared in
namespace std */
int main()
{
using std::vector; //using declaration
vector vi; /* interpreted as std::vector
vi; */
}
A using directive renders ALL the members of a namespace accessible. For example:


#include
#include /* iostream classes and operators
are also in namespace std */
int main()
{
using namespace std; /* using directive */
vector vi; // std::vector
vi.push_back(10);
cout << } std::cout>



COVARIANCE OF VIRTUAL MEMBER FUNCTIONS

An overriding virtual function has to match the signature and return type of the function it overrides. This restriction was recently relaxed to enable the return type of an overriding virtual function to co-vary with its class type. Thus, the return type of a public base can be changed to the type of a derived class. The covariance applies only to pointers and references. For example:


class B {
public:
virtual B* construct () const { return new B; }
};

class D {
public:
D* construct () const {return new D; }
};
Note that covariance of return type was added to C++ recently, so some compilers do not support it yet.

--------------------------------------------------------------------------------

 

NEVER RETURN A REFERENCE TO A LOCAL OBJECT

A reference is always bound to a valid object. When that object is destroyed, any use of its reference is undefined. The following example demonstrates that:


int & very_bad ()
{
int i = 1;
int &ri = i;
return ri; /* undefined */
}
The function returns a reference to a local variable, which is destroyed when the function exits. Any consequent attempt to use the dangling reference is undefined.

--------------------------------------------------------------------------------

 

RULES OF FUNCTION OVERLOADING

To overload a function, a different signature should be used for each overloaded version. A function signature consists of types of its arguments as well as their order. Here is a set of valid overloaded versions of a function named f:


void f(char c, int i);
void f(int i, char c);
void f(string & s);
void f();
void f(int i);
void f(char c);
Note, however, that a function differing only by its returned type is illegal:


int f(); /* error; differs only by return type */

--------------------------------------------------------------------------------

 

OVERLOADING A MEMBER FUNCTION ACROSS CLASS BOUNDARIES

The scope for overloading a member function is confined to the class containing this function. Sometimes, however, the need arises to overload the same function in a derived class. Using an identical name in a derived class merely hides the base class's function; it doesn't overload it:


class B {
public:
void func();
};
class D : public B {
public:
void func(int n); /* hiding B::f, not overloading it */
};
To overload--rather than hide--a function of a base class, the base's function name has to be injected explicitly into the namespace of the derived class by a using declaration:


class D : public B {
using B::func; /* inject the name of a base member
function into the scope of D */
public:
void func(int n); // overload
};

D d;
d.func ( ); // OK
d.func (10); // OK

--------------------------------------------------------------------------------

 

THE MOTIVE BEHIND NAMESPACES

To understand why namespaces were added to C++, imagine that the file system on your computer didn't have directories and subdirectories. All files would be stored in a flat repository, visible to every user and application. Consequently, extreme difficulties would arise: Filenames would clash, and simple actions like listing, copying, or searching files would be much more difficult. In addition, security would be severely compromised.

Namespaces in C++ are equivalent to directories in a file system. They avoid clashes, and they enable you to group related declarations in distinct logical units, or namespaces.

--------------------------------------------------------------------------------

 

USING NAMESPACES

Picking short and elegant names for classes, functions, variables, and constants can eventually cause name conflicts, because the same identifier might denote different entities that happen to share an identical name. For example, a class called "vector" can be declared in a container header file, but another class, also called "vector," can be found in a mathematical library. When these two distinct classes are used at the same project, their identical names can result in ambiguities that compilers and linkers cannot handle.

In the pre-namespace era, the only workaround was to use affixes in identifiers' names, as in the following example:


class myproj_vector /*a long name is used to avoid
conflicts*/
{ /*...*/};

class yourproj_vector /*another type of vector */
{ /*...*/};
This practice, however, is tedious and error prone. Furthermore, in many cases, the conflicting names appear in third-party code libraries and frameworks, which the user cannot change. Namespaces enable you to use convenient and intelligible names safely. Instead of repeating the unwieldy affixes, you can group your declarations in a namespace and factor out the recurring affix. For example:


namespace math_lib
{
class vector {/*..*/};
}
namespace container_lib
{
class vector {/*..*/};
}

--------------------------------------------------------------------------------

 

VISIT BJARNE STROUSTRUP'S HOME PAGE

If you are eager to know more about Bjarne Stroustrup, the creator of C++, you can visit his home page at

http://www.research.att.com/~bs

There you will find FAQs answered by Bjarne, links to many useful C++ sites, an index of his articles and books, and biographical details.

--------------------------------------------------------------------------------

 

CONSTRUCTORS OF FUNDAMENTAL TYPES

You can initialize variables of fundamental types by invoking their constructor explicitly, as in the following example:


void f()
{
int n = int(); /* zero initialized */
char c = char(); /* ditto */
double d = double(0.333);
short *ps = new short(0); /* ps points at a
zero-initialized short */
}

--------------------------------------------------------------------------------

 

GLOBAL OBJECTS--CONSTRUCTION AND DESTRUCTION

In general, global objects are considered harmful. In C++ in particular, there's even one more reason to think twice before using them: The construction of global objects takes place before the program's outset. Therefore, any exception thrown from a global object's constructor can never be caught. Likewise, the destruction of a global object conceptually takes place after the program's termination, so any exceptions that occur during destruction aren't handled either. (Note that throwing an exception from a destructor is never a good idea, even for nonglobal objects.)

--------------------------------------------------------------------------------

 

DECLARE NONMODIFIABLE FUNCTION PARAMETERS AS CONST

Passing large objects by value as function arguments is very inefficient. The alternative--that is, passing objects to a function by reference--is efficient, but it enables the function to change its arguments. To disable undesirable modifications to an argument, a function should declare the corresponding parameter as const. Often users can access only the function's prototype, but they have no access to the function's definition. By declaring every nonmodifiable argument as a const, the implementer provides the necessary documentation. An additional advantage in declaring a nonmodifiable parameter as const is the compiler's ability to detect bugs such as this:


class File
{
//...
public:
File& operator = (const File&);
};

void file_copy( const File & source, File & target)
{
source = target; /*oops, should be: target = source;
fortunately, detected by the compiler*/
}

--------------------------------------------------------------------------------

 

TRANSLATION UNITS AND SOURCE FILES

A translation unit contains a sequence of one or more declarations. The C++ Standard uses the term "translation unit" rather than "source file" because a single translation unit can be assembled from more than one source file. For example, a source file and the header files that are included in it are a single translation unit.

--------------------------------------------------------------------------------

 

USE MULTIPLE INHERITANCE TO CONJOIN FEATURES

Derived classes can integrate the functionality of several base classes simultaneously by having multiple base classes. Trying to achieve the same effect using single inheritance can be very difficult, to say the least. In the following example, class PersistentDate is publicly derived from two base classes, namely Date and Persistent:


class Persistent { /*..*/};
class Date {/*...*/};
class PersistentDate: public Date, public
Persistent { /*..*/};

--------------------------------------------------------------------------------

 

USE PROTECTED CONSTRUCTORS TO DISABLE UNDESIRABLE OBJECT INSTANTIATION

To disable the creation of class instances, you can declare its constructor as a protected member. Other classes can still inherit from that class, but no objects can be instantiated. The same effect of blocking instantiation of a class can be achieved with pure virtual functions. However, when pure virtual functions aren't needed, you can use a protected constructor instead:


class CommonRoot
{
protected:
CommonRoot(){}
};

class Derived: public CommonRoot
{
public:
Derived() {/*..*/}
};

Derived d; /* OK */
CommonRoot cr; /* error: attempt to access a
protected member of CommonRoot*/

--------------------------------------------------------------------------------

 

WHAT'S IN NAME MANGLING?

Name mangling (the more politically correct term is "name decoration," although this is rarely used) is a method used by a C++ compiler to generate unique names for identifiers in a program. The exact details of the algorithm are compiler dependent, and they may vary from one version to another. Name mangling ensures that entities with seemingly identical names still get a unique identification. The resulting mangled name contains all the necessary information that may be needed by the linker, such as linkage type, scope, calling convention, and so on. For example, when a global function is overloaded, the generated mangled name for each overloaded version is unique. Name mangling is also applied to variables, member functions, data members, static members, and so on. This way, a local variable and a global variable with the same user-given name remain distinct.

--------------------------------------------------------------------------------

 

PSEUDO DESTRUCTORS

Fundamental types have a pseudo destructor. A pseudo destructor is a syntactic construct whose sole purpose is to satisfy the need of generic algorithms and containers. It is a no-operation code and has no real effect on its object. For example:


typedef int N;
int main()
{
N i = 0;
i.N::~N(); //1: pseudo destructor invocation
i = 1; //i was not affected by the invocation
of the pseudo destructor
return 0;
}
In the statement numbered 1, the pseudo destructor of the nonclass type N is explicitly invoked. Pseudo destructors enable you to write generic code that doesn't have to know the exact type of every argument. A good example is a generic container that can store elements of user-defined types as well as fundamental types.

--------------------------------------------------------------------------------

 

TYPE MATCHING RULES OF EXCEPTIONS

When an exception is thrown, the exception handling mechanism searches for an appropriate handler for it. The matching rules for exceptions are more restrictive than the matching rules for function overloading. Consider the following example:


try
{
throw 5;
}
catch (unsigned int) /* will not catch the
exception from previous try-block */
{
}
The exception thrown is of type int, whereas the handler expects an unsigned int. The exception-handling mechanism does not consider these to be matching types, and as a result, the thrown exception is not caught.

The matching rules for exceptions allow only a limited set of conversions. For an exception E and a handler taking T or T&, the match is valid if T and E are of the same type, or if E is publicly derived from T. If E and T are pointers, the match is valid if E and T are of the same type or if E is a pointer to an object publicly derived from T.

--------------------------------------------------------------------------------

 

C-STRING OR STD::STRING--PERFORMANCE CONSIDERATIONS

std::string has a data member that holds its size. Calculating the size of a string object is therefore a fast constant-time operation, which is independent of the number of characters stored in the string object. On the other hand, the performance of strlen() is proportional to the number of characters stored in a C-string. To conclude, when large strings are used and size computations are frequent, std::string is more efficient than a C-string.

--------------------------------------------------------------------------------

 

USES OF PRIVATE INHERITANCE

When a derived class inherits from a private base, the is-a relation between a derived object and its private base does not exist. For example:


class Mem_Manager {/*..*/};
class List: private Mem_Manager {/*..*/};

void OS_Register( Mem_Manager& mm);

int main()
{
List li;
OS_Register( li ); /*error, no conversion
from List & to Mem_Manager& */
}
Essentially, private inheritance is like containment. In the example above, class List has a private base, Mem_Manager, which is responsible for its necessary memory bookkeeping. However, List is not a memory manager by itself. Therefore, private inheritance is used.

--------------------------------------------------------------------------------

 

DESIGNING WRAPPER CLASSES OF LEGACY CODE

In many systems, legacy C code is combined with newer C++ code. A common (yet incorrect) practice is to wrap C functions in a single C++ class, which provides as its interface a series of operations mapped directly to the legacy functions. However, such a general wrapper class is not recommended. It allows indiscriminate access to a variety of unrelated operations. This isn't really an object-oriented solution.

A better design approach is to divide the legacy functions into meaningful, self-contained classes. For example, instead of wrapping a bunch of legacy networking functions in a large class, it is advisable to group these functions according to specific protocols.

--------------------------------------------------------------------------------

 

WHY CLASS STRING DOESN'T HAVE AN IMPLICIT CONVERSION TO CHAR*

Unlike MFC's CString, class std::string doesn't have an automatic conversion operator to char*. There are two reasons for this. First, implicit conversions can cause undesirable surprises when you least expect it. Legacy C code is combined with new C++ code in many systems. In its prestandardized form, C used char* as a generic pointer (void* was added much later). You can imagine what chaos an implicit conversion to char* can inflict on such systems. However, there is another reason for the lack of automatic conversion: C-strings are null-terminated, whereas a string object needn't be null-terminated. Therefore, an implicit conversion of a string object in a context requiring a null-terminated array of characters can be disastrous.

For these reasons, the C++ standardization committee did not include a conversion operator in class std::string. When such a conversion is needed, you can call string::c_str() explicitly.

--------------------------------------------------------------------------------

 

EXCEPTION SPECIFICATION IS NOT PART OF A FUNCTION'S TYPE

An exception specification is not considered part of the type of a function. Therefore, you cannot declare two distinct functions that differ only in their exception specification:


void f(int) throw (Y);
void f(int) throw (Z); /* error; redefinition
of 'void f(int)' */
For the same reason, declaring a typedef that contains an exception specification is also an error:


typedef void (*PF) (int) throw(X); //compile time error

--------------------------------------------------------------------------------

 

CHOOSE DISTINCT NAMES FOR MEMBER FUNCTIONS WHEN USING MULTIPLE INHERITANCE

When using multiple inheritance, you want to choose a distinct name for member functions in the base classes to avoid name ambiguity. For example:


class AudioStreamer {
public:
void Play();
};
class VideoStreamer {
public:
void Play();
};
class AudioVisual: public AudioStreamer, public
VideoStreamer {/*..*/};
AudioVisual player;
player.play(); /*error: AudioStreamer::play() or
VideoStreamer::play() ?*/
One way to overcome the ambiguity is to specify a qualified name:


Player.AudioStreamer::play(); //tedious
However, a preferable solution is the use of distinct member function names in the base classes:


class AudioStreamer
{
public:
void au_Play();
};
class VideoStreamer
{
public:
void vd_Play();
};

--------------------------------------------------------------------------------

 

A SHORTHAND FOR "CONSTRUCTOR" AND "DESTRUCTOR"

The abbreviated forms "ctor" and "dtor" (also "c-tor" and "d-tor") are widely used in C++ literature, newsgroups, magazines, and in the ANSI/ISO Standard. These are simply the shorter forms of "constructor" and "destructor," respectively.

--------------------------------------------------------------------------------

 

INITIALIZING THE ARGUMENTS OF A BASE OR EMBEDDED SUBOBJECT

When a constructor has to initialize the arguments of a base class or an embedded object, you must use a member initialization list, as in the following example:


class base
{
private:
int num1;
public:
base(int n1); /*no default c-tor */
};

class derived : public base
{
public:
derived (int n) : base(n) {} /*initialize args
of base class ctor*/
};

--------------------------------------------------------------------------------

 

C/C++ USERS' JOURNAL SITE

The C/C++ Users' Journal is available online at

http://www.cuj.com

and is updated on a monthly basis. You can find articles, questions and answers, product reviews, and other C and C++ related information there. You can also search the online archive for articles from previous issues.

--------------------------------------------------------------------------------

 

DETECTING THE ENDIAN-NESS OF YOUR PLATFORM

The term "endian" refers to the way a computer architecture stores the bytes of a multibyte number in memory. If bytes at lower addresses have lower significance (Intel microprocessors, for instance), this is called "little endian" ordering. Conversely, "big endian" ordering describes a computer architecture in which the most significant byte has the lowest memory address. This distinction is chiefly important in network programming and mobile objects. The following program detects the endian-ness of the machine on which it is executed:


union probe{
unsigned int num;
unsigned char bytes[sizeof(unsigned int)];
};
int main()
{
probe p = { 1U }; //initialize union
bool little_endian = (p.bytes[0] == 1U); /*on a
big endian architecture, p.bytes[0] equals 0*/
}

--------------------------------------------------------------------------------

 

NESTING NAMESPACES

Namespaces can be nested. Nesting is useful in large-scale projects in
which every development team gets a dedicated namespace within the
project's namespace:

namespace proj /* entire project */
{
namespace dba_team /* nested; database administration team
sub-namespace */
{
class Connection {}; /* database connection */
}
namespace comm_team /*communication team's s sub-namespace */
{
class Connection{}; /* TCP connection */
}
};

Although two distinct classes called Connection are used in the same
project, they still can be used without name clashes since each of
them is declared in a dedicated sub-namespace:

int main()
{
proj::dba_team::Connection dbsession;
proj::comm_team::Connection TCP_link;
}


----------------------------------------------

DESTRUCTORS SHOULD HANDLE EXCEPTIONS LOCALLY

As a rule, a destructor should never throw exceptions because it may
have been invoked due to another exception; in this event, the program
is immediately terminated. Therefore, when a destructor calls a
function that may throw an exception, it is important to handle that
exception inside the destructor body:

void cleanup() throw (int);

C::~C()
{
try
{
cleanup(); /* exception might be thrown */
}
catch(int)
{
/* handle the exception */
}
}

In the example, the exception that might be thrown from cleanup() is
handled inside the destructor. If the exception weren't handled by the
destructor, it would propagate outside the destructor.


----------------------------------------------

 

 

DECLARE FRIENDS PUBLICLY

The access specifier of friend declarations is ignored by the
compiler. However, declaring friends explicitly with the "public"
access specifier is recommended to make the code more readable:

bool operator == (const Date& d1, const Date& d2);

class Date
{
private:
int year;
int month;
int day;
public:
friend bool operator == (const Date& d1, const Date& d2);
};


----------------------------------------------

 

AVOID IMPROPER INHERITANCE

Using public inheritance with classes that do not fulfill the is-a
relation (although they may be related to one another in some ways) is
a common design error.

Thus, using public inheritance to derive Stack from List is not a good
idea. Indeed, both classes provide some common operations. However, a
stack is NOT a list. Using public inheritance is too strong a
statement about the relationship between the two. It implies, for
example, that a function that takes a List argument can also take a
Stack. Private inheritance or, better yet, a simple containment would
be more suitable in this case.


----------------------------------------------

 

AVOIDING A COMMON PITFALL WITH OCTAL NUMERALS

A literal integer proceeded by 0 (zero) is an octal numeral. When
using octal numbers, beware of the following common pitfall:

const int warning = 10;
const int error = 100,

switch (status)
{
case 010 /* OOPS, decimal value is 8, not 10 */
handle_warning (); /* unreachable code! */
break;
case 100:
handle_error ();
break;
default:
break;
}

In the example, the programmer's intention was to format the
case-labels in a uniform style by padding the first case-label with a
preceding zero so that it also occupies three characters. However, the
compiler treats the zero-preceded label as an octal number, whereas
the other case-labels, which are not preceded with a zero, are treated
as decimal integers.


----------------------------------------------

 

USING NONVIRTUAL MULTIPLE INHERITANCE

Virtual inheritance is used to avoid multiple copies of a base class
in a multiply inherited object. However, there are cases where
multiple copies of a base are needed in a derived class. In such
cases, virtual inheritance is intentionally avoided. A concrete
example can demonstrate that. Suppose you have a scrollbar class that
serves as a base for two other subclasses:

class Scrollbar
{
private:
int x;
int y;
public:
void Scroll(int n);
//...
};
class HorizScrollbar : public Scrollbar {/*..*/};
class VertScrollbar : public Scrollbar {/*..*/};

Now imagine a window that has both vertical and horizontal scrollbars.
Such a window can be implemented and used in the following way:

class MultiScroll: public VertScrollbar,
public HorizScrollbar {/*..*/};
MultiScroll ms;
ms.HorizScrollbar::Scroll(5); //scroll left
ms.VertScrollbar::Scroll(12); //...and up

To be able to scroll a MultiScroll up and down as well as left and
right, a MultiScroll object needs two distinct Scrollbar subobjects.
Therefore, virtual inheritance is intentionally avoided in this case.


----------------------------------------------

 

AUTOMATIC TYPE DEDUCTION OF A ZERO INITIALIZER

The literal 0 is an int. However, it can be used as a universal
initializer for every fundamental data type, since it is automatically
cast to the appropriate type. Zero is a special case in this respect:
The compiler examines the context of the initialization/assignment
expression to determine its actual type. For example:

void *p = 0; /* 0 treated as a null pointer */
char name[10] = {0}; /* 0 cast to a '\0' */
void (*pf)(int) = 0; /* 0 as a null pointer to function */


----------------------------------------------

 

THE LIFETIME OF A BOUND TEMPORARY OBJECT

You can safely bind a reference to a temporary object since the
temporary object to which the reference is bound persists for the
lifetime of the reference. For example:

class A
{
public:
A(int i);
};

int main()
{
A& ref = A(5); /* bind a reference to a temp */
A a2 = ref; /* use bound reference safely */
}/* temp destroyed along with its bound reference */

In the example, the destruction of the temporary object to which ref
is bound is deferred until ref goes out of scope--that is, when main()
exits.


----------------------------------------------

 

THE SIZE OF A COMPLETE OBJECT IS NEVER ZERO

An empty class doesn't have any data members or member functions.
Seemingly, the size of an object of such a class should be zero.
However, the Standard states that the size of a complete object shall
never be zero:

class Empty {};
Empty e; /* e occupies at least 1 byte of memory */

There is a good reason for this rule. If a complete object were
allowed to occupy zero bytes of memory, its address could overlap with
the address of a different object. Consequently, elements of an array
of empty objects would all have identical memory addresses. To avoid
this, a complete object must occupy at least one byte of memory. Note
that this restriction does not apply to incomplete objects (for
example, a base class subobject in a derived class), which may occupy
zero bytes.


----------------------------------------------

 

DEFINING WIDE CHARACTER LITERALS

A string literal is a sequence of one or more characters enclosed in
double quotes:

const char txt[] = "ABC"; /* a char literal */

By default, the compiler assumes a char-based literal. To create a
wide character string literal, prefix the letter L to the quoted
string:

const wchar_t wtxt[] = L"ABC"; /* now a wchar_t literal*/

The compiler is case sensitive; therefore, a capital L has to be used.
To see the differences in the underlying types (char vs. wchar_t), you
can examine the different results of sizeof(txt) and sizeof(wtxt).


----------------------------------------------

 

EXCEPTIONS THROWN DURING OBJECT CONSTRUCTION AND MEMORY LEAKS

Operator new performs two operations: It allocates memory from the
free store, and it constructs an object on the allocated memory. Does
the allocated memory leak when an exception is thrown during the
construction process?

No, it doesn't. The allocated memory is returned to the system before
the exception propagates to the program. Thus, an invocation of
operator new can be construed as two consecutive operations, the first
of which merely allocates a properly aligned memory block with a
sufficient size. If this operation was successful, the second
operation begins, which consists of invoking the object's constructor
on the memory address retained in the previous step. If an exception
occurs during the object's construction, the previously allocated
memory is automatically released, and only then does the exception
propagate to the program.


----------------------------------------------

 

DO NOT USE OPERATOR NEW IN A THROW STATEMENT

Dynamic allocation of an exception (as in the example below) is a bad
idea:

class Err
{
public:
Err(const char * description);
};

void f()
{
if (disaster)
throw new Err("failed"); /* bad idea: exception allocated on the
free store */
//...
}

There are at least two problems with this approach. First, an
exception may be thrown due to memory exhaustion. In this case, trying
to allocate more memory from the free store will surely fail (with
disastrous results). The second problem is more common: The allocated
memory is not released. As a result, memory leaks occur. Creating a
temporary exception object is a much better technique:

if (disaster)
throw Err("failed"); /* temporary object*/

It is guaranteed that the temporary object persists until the
appropriate handler that handles the exception has completed. By
creating a temporary exception object, you're not taking the risk of
allocation failure, and your programs do not incur memory leaks.


----------------------------------------------

 

PROPERTIES OF STATIC STORAGE

Global objects, static data members of a class, namespace variables
and objects, and static variables in functions have static storage
type. The memory address of a static object remains the same
throughout the program's execution (unlike automatic objects'
addresses). Every static object is constructed only once during the
program's execution. By default, static variables are initialized to
binary zeros. Static objects are also zero-initialized before a
subsequent initialization by the constructor. Examples of objects with
static storage are:

int num; /*global variable */

int func()
{
static calls; /* local static variable */
return ++calls;
}

class C
{
private:
static bool b;
};
C::b; /* static data member */

namespace NS
{
std::string str; /* namespace object */
}


----------------------------------------------

ABSTRACT DATA TYPES AND ABSTRACT CLASSES

The terms "abstract data type" and "abstract class" refer to two
entirely different concepts, although both of them use the word
"abstract" because of an historical accident. An abstract data type
(also called a "concrete type") is a self-contained, user-defined
class that bundles data with a set of related operations. However, it
does not inherit from another class, nor does it serve as the base for
other classes. std::string, std::complex, and std::vector are all
abstract data types, or concrete types.

In contrast, an abstract class is anything but an abstract data type.
An abstract class is a class that has at least one pure virtual member
function. It is not a data type (normally, abstract classes do not
contain any data members), nor can you instantiate an object thereof.
An abstract class is merely a skeletal interface, which specifies a
set of services that its subclasses implement. Unfortunately, the
distinction between the two concepts is often confused. Many people
erroneously use the term "abstract data type" when they mean an
abstract class.


----------------------------------------------

 

WHAT "STACK UNWINDING" MEANS

When an exception is thrown, the implementation first searches for an
appropriate handler (a catch statement) for it in the current scope.
If such a handler does not exist, the current scope is exited, and the
function that is higher in the calling chain is entered into scope.
This process is iterative--it continues until an appropriate handler
has been found, or if there's no matching handler, until main() has
been reached. An exception is considered to be handled upon its entry
to a handler. When the handler is starting to execute, all the local
objects that were constructed on the path from the try block to the
throw statement have been destroyed. In other words, the stack is said
to have been "unwound."


----------------------------------------------

RELOCATING DECLARATIONS AS AN OPTIMIZATION MEASURE

On some occasions, the performance boost that can result from moving
declarations is considerable. Check out the following example:

void use()
{
std::string s1;
if (condition == false)
return; /* s1 was not needed */
s1 = "ABC"; /* s1 used only here */
return;
}

The local object s1 is unconditionally constructed and destroyed in
use(), even if it is not used at all. As you can see, when the
condition in the if-statement is false, the unnecessary construction
and destruction of s1 are still unavoidable. Nevertheless, the
unnecessary construction and destruction of s1 can be eliminated. All
that is required is the relocation of the declaration of s1 to the
point where it is actually used:

void use()
{
if (condition == false)
return;
std::string s1; /* moved */
/* use s1 here*/
return;
}

Now the object s1 is constructed only when it is really needed. When
it is not needed, s1 is neither constructed nor destroyed. Simply by
moving the declaration of s1, you eliminate two unnecessary member
function calls.


----------------------------------------------

 

INLINE ISSUES OF CONCERN

Two conundrums are associated with inline functions. The first has to
do with maintenance. A function can begin its life as a slim inline
function, offering the benefits that are associated with inlining. At
a later phase in the lifetime of the application, the function body
can be extended with additional functionality because of changes in
the implementation of its class. Suddenly, inlining becomes
inefficient or even impossible. It is therefore important to
reconsider the removal of the inline specifier from functions that are
extended. For member functions defined in the class body, the change
requires that the function definition be moved to a separate source
file.

Another problem can arise with inline functions in third-party code
libraries. It is impossible to maintain binary compatibility if the
definition of an inline function changes. In this case, the users must
recompile their code to reflect the change. For a non-inline function,
the users only need to relink their code.


----------------------------------------------

THE SIZE OF AN ENUM TYPE IS NOT FIXED

In C, the size of an enumeration equals the sizeof(int). In C++, the
underlying type for an enumeration is not necessarily an int--it can
be smaller. Furthermore, if an enumerator's value is too large to be
represented as an unsigned int, the implementation is allowed to use a
larger unit. For example:

enum Distance
{
Jove = 5000000000, /* stored in a 64-bit integer */
Moon =300000
};

Therefore, do not assume that an enum necessarily occupies the same
size as an int.


----------------------------------------------

THE DEFAULT RETURN VALUE OF MAIN()

Consider the following program:

int main()
{
func();
}

The programmer did not write an explicit return statement inside
main(). In C, when control reaches the end of main() without
encountering a return statement, an undefined value is returned to the
environment. In C++, however, main() implicitly returns 0 in this
case.


----------------------------------------------

OVERLOADING THE SUBSCRIPT OPERATOR PROPERLY

When you overload operator [], remember to define two versions
thereof: a non-const version and a const one:

class MyString
{
private:
char * buff;
int size;
public:
char& operator [] (int n) { return buff[n]; }
const char& operator [] (int n) const { return buff[n]; } /* const
*/
};

The const version of the subscript operator is needed when its object
is itself const:

void f(const MyString& str)
{
char c = str[0]; /* const char& operator [] (int n) const */
}


----------------------------------------------

Y2K-COMPLIANT DATE FORMATS

Fortunately, the < time.h > functions and data structures are Y2K
compliant in general, so legacy code should work satisfactorily in the
year 2000. However, to ensure Y2K compliance, make sure that date and
time formatting functions display a four-digit year. For example, the
standard function

size_t strftime(char *str,
size_t max,
const char *fmt,
const struct tm* ptm);

formats a tm struct according to the format specified in fmt and
stores the result in no more than max bytes (including a terminating
\0) in the buffer str. The format string may contain symbols like the
following (this is a partial list):

%a /* name of weekday */
%b /* name of month */
%d /* numeric day of month, counting from zero */

Additional symbols exist for the hour, minutes, seconds, and so forth.
However, the crucial elements for Y2K compliance are

%y /* two digit year, without the century */
%Y /* full four digit year */

To avoid any potential Y2K problems, you should always use the %Y
format symbol rather than %y. Note also that if you apply this change
to legacy code, you have to ensure that the buffer is large enough to
hold two extra characters.


----------------------------------------------

CAVEATS OF POINTER ARITHMETIC PAST AN ARRAY'S END

In C and C++, the address of the first element past an array's end can
be used in pointer arithmetic. Thus, you can initialize a vector with
the contents of a built-in array as follows:

int arr[3] = {1, 2, 3};
vector < int > vi ( arr, /* array's beginning */
arr + 3 ); /* one element past the
array's end */

Now consider an almost identical version of this example:

vector < int > vi ( & arr[0],
& arr[3] );

Seemingly, both forms are interchangeable--well, not exactly. While
the Standard allows you to take the address of one element past the
array's end, that's not what the second example does. Notice how the
expression & arr[3] is interpreted. First, the subexpression arr[3] is
evaluated to *(arr+3); that is, the element located one position past
the array's end is read. However, no such element exists. Next, the
address of the nonexistent element is taken. In other words, & arr[3]
is equivalent to & (*(arr+3)), which is different from arr+3.

The first example is perfectly legal, while the second one is
undefined (although in practice, it will work on most compilers as
expected). Let's hope that this loophole will be fixed in the upcoming
revision of the Standard.


----------------------------------------------

STANDARD SPEAKING: WHAT IS A SIDE EFFECT?

A "side effect" is a change in the state of the execution environment.
Modifying an object, accessing a volatile object, invoking a library
I/O function, and calling a function that does any of these operations
are all side effects. It is important to understand what a side effect
is, since the validity of many operations depends on the existence of
a side effect (or lack thereof).


----------------------------------------------

THE PERILS OF MACROS

Even macros that look harmless can have detrimental effects. For
example:

#define twice(x) ((x)+(x))

The macro twice is well parenthesized and performs a simple addition
operation. Can it go wrong? You bet. When twice takes an argument with
side effects, the results are undefined:

int n = 1;
int sum;
sum = twice(++n); //guess what?

Since ++n equals 2, one might expect (rather naively) that sum would
be 4 after the assignment. However, the expression twice(++n) is
expanded as ((++n)+(++n)), which yields an undefined value. Note that
if an ordinary function had been used instead of a macro, as in

inline int twice(int x) { return x+x; }
sum = twice(++n); //now well defined

the result would have been 4, as expected.


----------------------------------------------

 

USE THE CONDITIONAL OPERATOR SPARINGLY

The conditional operator, ?, is shorthand for an if-else sequence of
statements. Although the conditional operator has legitimate uses, in
many cases programmers tend to misuse it, producing unintelligible
code. Sometimes, the use of the conditional operator yields undefined
behavior. Consider the following example:

int n = 1;
n = (n != 0) ? n++ : 0;

What is the value of n after the second statement executes? One can't
say. Not only is the expression n = (n != 0) ? n++ : 0; unreadable, it
also yields undefined behavior, because when n is not 0, the
expression evaluates to n = n++; which is undefined. Note that even a
well-behaved version of this expression:

n = (n != 0) ? (n+1) : 0; /* well-behaved, still cryptic*/

is still unreadable. Now consider a much simpler and well-behaved form
that does exactly what the convoluted n = (n != 0) ?(n+1) : 0; was
supposed to do:

if (n != 0)
n++;

Thus, using the conditional operator to save a few keystrokes is a bad
idea. Most of the time, you will find that a plain if-else sequence is
more intelligible, efficient, and well behaved.


----------------------------------------------

 

TIME REPRESENTATION

C and C++ represent time as a positive integer that contains the
number of seconds elapsed since January 1, 1970. The type time_t
(defined in the header < ctime > or < time.h > in C) is a
platform-dependent signed integral type that has at least 32 bits. As
large as this unit may seem, a 32-bit time_t will roll over on January
18, 2038. As a result, applications that use time measurements beyond
that date (such as mortgage calculations, pension plans, and health
insurance) may encounter undefined behavior. Let's hope that before
this problem becomes critical, hardware architectures will have
switched to a 64-bit time_t, which is sufficiently large to represent
billions of years. To examine the size of time_t on your machine,
simply use the expression sizeof(time_t).


----------------------------------------------

GUARANTEES ABOUT THE SIZES OF INTEGRAL TYPES

The integral types char, short, int, and long have
implementation-defined sizes. However, there are a few guarantees in
Standard C and C++ about their sizes: char must occupy at least 8
bits, and it may not be larger than short; short must occupy at least
16 bits, and it may not be larger than int; and long must be at least
as large as int, and it must occupy no less than 32 bits.


----------------------------------------------

GUARANTEES ABOUT THE SIZES OF INTEGRAL TYPES

The integral types char, short, int, and long have
implementation-defined sizes. However, there are a few guarantees in
Standard C and C++ about their sizes: char must occupy at least 8
bits, and it may not be larger than short; short must occupy at least
16 bits, and it may not be larger than int; and long must be at least
as large as int, and it must occupy no less than 32 bits.


----------------------------------------------

A VIRTUAL MEMBER FUNCTION CAN BECOME PURE VIRTUAL IN A DERIVED CLASS

In general, a derived class that inherits a pure virtual member
function from its abstract base class implements this function.
However, the opposite is also true: A virtual member function that is
implemented in a base class can become pure virtual in a derived
class. For example:

class Base
{
virtual void func() { /*do something */ }
};

class Derived : public Base
{
virtual void func() = 0; /* redefined as pure virtual */
};

You wouldn't normally do that. Sometimes, however, this is the only
way to overcome flaws in a base class that you are not allowed to fix.


----------------------------------------------

INTEGRAL TYPES WITH PORTABLE SIZES

The actual size of built-in integral types such as char, short, int,
and long is machine dependent. When you need portable sizes, you can
use the following standardized typedefs instead:

int8 //signed 8 bits
int16
int32

These integral types are defined in the standard header < cstddef >
(the corresponding C header is < stddef.h > ). Many platforms also
define int64. Unsigned versions of these typedefs also exist:

uint8 //unsigned 8 bits
uint16
uint32


----------------------------------------------

MINIMIZE THE USE OF DYNAMIC MEMORY ALLOCATION

Pointer-based operations are less frequently needed than they may
seem. For example, examine the following class:

class PointerMisuse
{
private:
CDC * m_pDeviceContext;
public:
PointerMisuse();
~PointerMisuse();
};

PointerMisuse::PointerMisuse()
{
m_pDeviceContext = new CDC;
}

PointerMisuse::~PointerMisuse()
{
delete m_pDeviceContext;
}

Even experienced programmers are often inclined to use this
programming style, allocating objects on the free store. This is not
surprising--after all, classes like this are widely used in many
commercial frameworks (MFC, ATL, and many others). But is the use of
pointers and dynamic memory allocation really necessary here? It
isn't. This class can be rewritten as follows:

class ProperUse
{
private:
CDC * m_pDeviceContext;
CDC cdc; //automatic storage
public:
ProperUse();
};

ProperUse::ProperUse()
{
m_pDeviceContext = & cdc;
}

Not only is this version safer (remember that dynamic memory
allocations might fail), it is also simpler (for example, no
destructor is required) and more efficient because it avoids the
unnecessary overhead of memory allocation and deallocation at
run-time.


----------------------------------------------

GET A FREE C++ COMPILER FOR WINDOWS

Dev-C++ is a free graphical C++ compiler for Windows 95, 98, and NT.
The package also includes a debugger and source file editor that you
can use as a complete development suite. You can download it from

http://www.bloodshed.nu/devc.html


----------------------------------------------

THE MEMORY LAYOUT OF IDENTICAL STRING LITERALS

Whether identical string literals are treated as distinct objects
depends on the implementation. Consider the following code snippet:

extern const char msg1[] = "hello";
extern const char msg2[] = "hello";
int main()
{
bool eq = (msg1 == msg2); //true or false?
return 0;
}

Some implementations might store the constants msg1 and msg2 at the
same memory address (on such implementations, the value of the
variable eq is true). Other implementations store msg1 and msg2 in two
distinct memory addresses, and, in this case, eq is false.


----------------------------------------------

UNDERSTANDING MEMORY ALIGNMENT

Most CPUs require that objects and variables reside at particular
offsets in the system's memory. For example, 32-bit processors require
that a 4-byte integer reside at a memory address that is evenly
divisible by four. This requirement is called "memory alignment." On
such architectures, a 4-byte int can be located at memory address
0x2000 or 0x2004, but not at 0x2001. On most UNIX systems, an attempt
to use misaligned data results in a bus error, which terminates the
program altogether. On Intel processors, the use of misaligned data is
supported, but at a substantial performance penalty. Therefore, most
compilers automatically align data variables according to their type
and the particular processor being used. This is why the size that
structs and classes occupy may be larger than the sum of their
members' size:

struct Employee
{
int ID;
char state[3]; /* CA, NY + null */
int salary;
};

Apparently, Employee should occupy 11 bytes (4+3+4). However, most
compilers add an unused padding byte after the field "state" so that
it aligns on a 4-byte boundary. Consequently, Employee occupies 12
bytes rather than 11. You can examine the actual size by using the
expression sizeof(Employee).


----------------------------------------------

CONSTRUCTING OBJECTS ON PREALLOCATED CHAR ARRAYS

The memory buffer returned by operator new is guaranteed to have the
suitable alignment properties for any type of object. This property
enables you to construct objects on a preallocated char array. For
example:

#include < new >
#include < iostream >
using namespace std;

class Employee{ /*...*/};

void f()
{
char * pc = new char[sizeof(Employee)]; /*pre-allocate*/
Employee *pemp = new (pc) Employee; /* constructed on char array */
//...use pemp
pemp -> ~Employee(); /* explicit destruction */
delete [] pc; /* release buffer */
}


----------------------------------------------

DELETING MULTIDIMENSIONAL ARRAYS

You can allocate a multidimensional array dynamically, as in the
following example:

int*pp[10]; /* array of ten pointers */
for (int j=0; j < 10; j++) /* allocate sub-arrays */
{
pp[j] = new int[5]; /* every element in pp points to an array of 5
int's */
}
pp[0][0] = 1; /* use pp as a multi-dimensional array */

Remember that you have to use a loop to delete a multidimensional
array:

for (int k=0; k < 10; k++)
{
delete [] pp[k]; /* delete [] every element in pp */
}

Never use the following forms to delete a multidimensional array:

delete pp; /* undefined behavior */
delete [] pp; /* undefined behavior */

None of them ensures that the elements of the multidimensional array
are properly destroyed.


----------------------------------------------

USE STATIC ALLOCATION FOR BUFFERS WITH A FIXED SIZE

Imagine that you have to write a stock quote application that accepts
stock symbols and retrieves their current values. Using a std::string
object to represent a stock symbol is inefficient because std::string
allocates an initial buffer that has 16 to 64 characters, depending on
the platform. Since a stock symbol never reaches this size, using
std::string is overkill and causes a substantial waste of memory. At
this point during the design process, designers often suggest writing
a custom string class that handles small strings. This is a plausible
suggestion, but a common design mistake is to let the custom string
class allocate memory from the free store. Free store allocation is
significantly slow. In addition, remember that even if you allocate a
single byte using operator new, the cost is much more than a single
byte, because the system stores internal bookkeeping information in
the allocated buffer, which therefore has to be much larger. A better
design approach is to create a buffer on the stack with the size of
the largest possible string. For example:

class QuoteString
{
private:
char stock[6]; //fixed size
public:
//...
};


----------------------------------------------

OVERLOADING THE FUNCTION CALL OPERATOR

Overloading the function call operator can be confusing because the
overloaded operator has two pairs of parentheses; it may not be
immediately obvious which of them declares the parameters. Another
point to note is that the overloaded function call operator may take
any number of arguments, unlike other overloaded operators. The
following example shows how you can overload the () operator and use
it:

class A
{
private:
int n;
public:
//...
/* parameters appear in second pair of parentheses */
void operator ()(bool debug) const;
};

void A::operator ()(bool debug) const //definition
{
if (debug)
display(n);
else
display ("no debug");
}
int main()
{
A a;
a(false); /* use overloaded operator */
a(true);
}


----------------------------------------------

THE ROLE OF IMPLICITLY DECLARED CONSTRUCTORS

If a class has no user-declared constructor, and it doesn't have const
or reference data members either, C++ implicitly declares a default
constructor for it. An implicitly declared default constructor is an
inline public member of its class. It performs the initialization
operations that are needed by the implementation to create an object
instance. Note, however, that these operations do NOT involve
initialization of user-declared data members or allocation of memory
from the free store. For example:

class C
{
private:
int n;
char *p;
public:
virtual ~C() {}
};

void f()
{
C obj; //OK
}

The programmer did not declare a constructor in class C. Therefore,
the implementation created an implicit default constructor for C. The
synthesized constructor does not initialize the data members n and p.
These data members have an indeterminate value after obj has been
constructed. However, the synthesized constructor initialized a
pointer to the virtual table of class C, to ensure that the virtual
destructor can be called.


----------------------------------------------

THE C_STR() AND DATA() MEMBER FUNCTIONS OF STD::STRING

Class std::string provides two member functions that return the const
char * representation of its string, namely string::c_str() and
string::data(). c_str() returns a null-terminated const pointer to
char that represents the object's string. For example:

void f()
{
bool identical;
string s = "Hello";
if(strcmp( s.c_str(), "Hello") == 0)
identical = true;
else
identical = false;
}

The member function string::data() also returns a const char *
representation of its string, but it might not be null-terminated, so
data() should not be used in a context that requires a null-terminated
character array.


----------------------------------------------

GUIDELINES FOR WRITING PORTABLE CODE

Contrary to what most people believe, portability doesn't guarantee
that the same code can be compiled and run on every platform without
any modifications. Although it's sometimes possible to write 100
percent portable code, in nontrivial applications such code is usually
too complex and inefficient. A better approach is to separate
platform-specific modules from the platform-independent parts of the
entire application. GUI components, for instance, tend to be very
platform specific, and therefore should be encapsulated in dedicated
classes. On the other hand, business logic and numeric computations
are more platform independent. Therefore, when the application is to
be ported, only the GUI modules need to be rewritten, while the rest
of the modules can be reused.


----------------------------------------------

COPY CTOR AND ASSIGNMENT OPERATOR

Although the copy constructor and assignment operator perform similar
operations, they are used in different contexts. The copy constructor
is invoked when you initialize an object with another object:

string first "hi";
string second(first); //copy ctor

On the other hand, the assignment operator is invoked when an already
constructed object is assigned a new value:

string second;
second = first; //assignment op

Don't let the syntax mislead you. In the following example, the copy
ctor--rather than the assignment operator--is invoked because d2 is
being initialized:

Date Y2Ktest ("01/01/2000");
Date d1 = Y2Ktest; /* although = is used, the copy ctor invoked */


----------------------------------------------

EVALUATION ORDER OF A MEMBER-INITIALIZATION LIST

Whenever you use a member-initialization list, the compiler transforms
the list so that its members are laid down in the order of declaration
of the class data members. For example, the following class declares
two data members: a and b. However, the constructor's
member-initialization list first initializes the member b and then a:

class A
{
private:
int a;
int b;
public:
A(int aa, int bb) : b(bb), a(aa) {} /* reverse order */
};

Since a is declared before b, the member-initialization list is
automatically transformed by the compiler into

A(int aa, int bb) : a(aa), b(bb) {}/* transformed by the compiler to
fit class declaration order */

While the transformation of initializers is harmless in this case,
imagine what happens with a slightly different member-initialization
list:

A(int bb) : b(bb), a(b) {} /* original code */

The compiler implicitly rearranges the list into

A(int bb) : a(b), b(bb) {} /* oops: 'a' has an undefined value */

Although some compilers catch this potential bug, many compilers
don't. Therefore, it's best to adhere to the class order of
declaration in a member-initialization list.


----------------------------------------------

USING A TEMPLATE AS A TEMPLATE'S ARGUMENT

You can use a template as a template's argument. In the following
example, a mail server class can store incoming messages in a vector
of messages, where each message is represented as a vector of bytes:

vector < vector < unsigned char > > vmessages;

Note that the space between the left two angle brackets is mandatory.
Otherwise, a sequence of two consecutive > signs is parsed as the
right shift operator. A typedef can be used to improve readability
both for the compiler and the human reader:

typedef vector < unsigned char > msg;
vector < msg > vmessages;


----------------------------------------------

ALTERNATIVE REPRESENTATIONS OF OPERATORS

C++ defines textual representations for logical operators. Platforms
and hardware equipment that do not support all the special characters
that are used as logical operators can use these alternative
representations. The alternative representations are also useful when
you want to transfer source files in a portable form to other
countries and locales, in which special characters such as ^ and ~ are
not part of the national character set. The following list contains
the alternative keywords and their equivalent operators:

and &&
and_eq &=
bitand &
bitor |
compl ~
not !
not_eq !=
or ||
or_eq |=
xor ^
xor_eq ^=


----------------------------------------------

BEFORE PROFILING YOUR APPS...

If you intend to optimize your software's performance, be sure to
profile the release version rather than the debug version. The debug
version of the executable contains additional code (about 40 percent
extra compared to the equivalent release executable) for symbol lookup
and other debug "scaffolding." In addition, most compilers have
distinct implementations of the run-time library--one for the debug
version and one for the release version. For example, the debug
version of operator new initializes the memory it allocates with a
unique value so that memory overruns can be detected automatically.
Conversely, the release version of new doesn't initialize the
allocated block. Furthermore, an optimizing compiler may have already
optimized the release version of an executable in several ways, such
as eliminating unneeded variables, loop unrolling, storing variables
in the CPU registers, and function inlining. These optimizations are
not applied to the debug version. Therefore, you can't deduce from a
debug version where the actual bottlenecks are located.

To conclude, debugging and optimization are two distinct operations.
Use the debug version to chase bugs and logical errors; profile the
release version to optimize it.


----------------------------------------------

UNDERSTANDING REFERENCE COUNTING

A reference counting class counts how many object instances have an
identical state. When two or more instances share the same state, the
implementation creates only a single copy and counts the number of
existing references to this copy. For example, an array of strings can
be represented as a single string object that holds the number of
elements in the array. Since initially the array elements share the
same state (they all are empty strings), only a single object is
needed to represent the array, regardless of its size. When one of the
array elements changes its state (the program writes a different value
to it), the reference counting object creates one more object--this is
called "copy on write." Under some conditions, reference counting can
boost performance considerably both in terms of run-time speed and
memory usage. For this reason, many implementations of std::string use
copy on write.


----------------------------------------------

TEMPLATES AND INHERITANCE

A common mistake is to assume that a vector < Derived > is like a
vector < Base > when Derived is a subclass of Base. For example:

class Base {/**/};
class Derived : public Base {/**/};
void func(vector < Base > & vb);
int main()
{
vector < Derived > vd;
func(vd); /* compilation error; pvd is not vector < Base > */
}

However, there is no relationship between classes generated from the
same class template. Therefore, the is-a relationship does not exist
between vector < Base > and vector < Derived >.


----------------------------------------------

TEMPLATES AND INHERITANCE

A common mistake is to assume that a vector < Derived > is like a
vector < Base > when Derived is a subclass of Base. For example:

class Base {/**/};
class Derived : public Base {/**/};
void func(vector < Base > & vb);
int main()
{
vector < Derived > vd;
func(vd); /* compilation error; pvd is not vector < Base > */
}

However, there is no relationship between classes generated from the
same class template. Therefore, the is-a relationship does not exist
between vector < Base > and vector < Derived >.


----------------------------------------------

WHEN TO USE A VOID-CAST

Some rigorous compilers issue a warning message if you ignore a
function's return value. For example:

int func();
int main()
{
func(); /* return value ignored */
}

Indeed, ignoring a function's return value may indicate a bug.
However, sometimes it's a deliberate and well-behaved practice. To
suppress the compiler warnings in such cases, you can explicitly cast
the return value to void:

int main()
{
(void) func(); /* suppress compiler warnings about ignored return
value */
}


----------------------------------------------

BEGIN() AND END() MEMBER FUNCTIONS OF STL CONTAINERS

All STL containers provide the begin() and end() pair of member
functions. begin() returns an iterator that points to the first
element of the container. For example:

vector < int > v(1);
v[0] = 10; /* assign the first element */
vector < int > ::iterator p = v.begin(); /* p points to v's first
element */

However, end() returns an iterator pointing one position past the last
valid element of the container. This sounds surprising at first, but
there's nothing really unusual about it if you consider how strings in
C are represented: an additional null character is automatically
appended one position past the final element of the char array. The
additional element in STL has a similar role--it marks the end of the
container and is automatically appended to the container so you should
not attempt to modify it. To get the last valid element of a
container, you should decrement the result of end() by 1, as in the
following example:

vector < int > ::iterator p = v.end()-1; /*p points to v's last
element */


----------------------------------------------

CONTAINER'S REALLOCATION AND ITS CONSEQUENCES

When a container has exhausted its free storage and additional
elements have to be inserted to it, the container reallocates itself.
The reallocation process consists of four steps. First, a new memory
buffer large enough to store the container is allocated. Second, the
container copy-constructs existing elements on the new memory
location. Third, the destructors of the original elements are
successively invoked. Finally, the original memory buffer is released.

Clearly, frequent reallocations can impose a significant performance
overhead. However, some techniques can minimize--and sometimes
avoid--reallocation. For example, when you create a vector object, you
can specify its size in advance:

vector < int > vi(1000); /* make room for at least 1000 elements */

You can also specify the size of the vector object after construction.
For example:

vi.reserve(2000); /* make room for at least 2000 elements */


----------------------------------------------

CONTAINER'S REALLOCATION AND ITERATOR INVALIDATION

When a container reallocates its elements, their addresses change
correspondingly. Consequently, the values of existing pointers and
iterators are invalidated:

#include < iostream >
#include < list >
using namespace std;

int main()
{
list < double > payroll;
payroll.push_back(5000.00);
list < double > ::iterator p = payroll.begin();
for (int i = 0 ; i < 10; i++)
{
payroll.push_back(4500.00); /* insert 10 more elements to payroll;
reallocation may occur */
}
/* DANGEROUS, p may have been invalidated */
cout < < "first element in payroll: " < < *p ;
}

In this example, it may well be the case that payroll reallocated
itself during the insertion of 10 additional elements, thereby
invalidating the iterator p. Using an invalid iterator is
undefined--it's exactly as if you were using a pointer with the
address of a deleted object. To be on the safe side, it is advisable
to reassign the iterator's value:

for (int i = 0 ; i < 10; i++)
{
payroll.push_back(4500.00);
}
p = payroll.begin(); /* re-assign p */
cout < < "first element in payroll: " < < *p; /* now safe */


----------------------------------------------

DISPLAYING THE LITERAL REPRESENTATION OF BOOL VARIABLES

By default, iostream objects display bool variables as 0 and 1. You
can override the default setting by inserting the formatting flag
boolalpha to the object stream. Subsequently, 'false' and 'true' will
be displayed instead of 1 and 0:

#include < iostream >
using namespace std;
int main()
{
bool b = true;
cout < < b; /* output '1' */
cout < < boolalpha; /* henceforth, use 'true' and 'false' */
cout < < b; /* output 'true' */
cout < < ! b; /* output 'false' */
}


----------------------------------------------

THE COPY ALGORITHM

The Standard Library provides a generic function, copy(), which you
can use for copying a sequence of objects to a specified target. The
first and the second arguments of copy() are const iterators that mark
the beginning and the end of the sequence being copied (or a fragment
of that sequence). The third argument points to a container into which
the sequence is to be copied. The following example shows how to copy
the elements of a list into a vector using the copy() algorithm:

#include < algorithm > /* definition of copy */
#include < list >
#include < vector >
using namespace std;

int main()
{
list < int > li;
vector < int > vi;
li.push_back(1); /* fill the list */
li.push_back(2);
vi.reserve( li.size() );
/* must make room for copied elements in advance */
copy (li.begin(), li.end(), vi.begin() );
/* copy list elements into vector, at vector's beginning */
}


----------------------------------------------

INTERACTING WITH THE OPERATING SYSTEM

In general, API functions and classes enable you to interact with the
operating system. Sometimes, however, it is much simpler to execute a
system command directly. For this purpose, you can use the standard
function system() that takes a const char * argument containing a
shell command. For example, on a DOS/Windows operating system, you can
display the files in the current directory like this:

#include < cstdlib >
int main()
{
system("dir"); /* execute the "dir" command */
}


----------------------------------------------

AVOID MISUSES OF EXCEPTION HANDLING

Although you can use exceptions instead of for-loops and break
statements, doing so is not a recommended programming practice.
Consider the following demo application that prompts the user to enter
data until a certain condition is met:

#include < iostream >
using namespace std;
class Exit{};
int main()
{
int num;
cout < < "enter a number; 99 to exit" < < endl;
try
{
while (true) /* infinite */
{
cin > > num;
if (num == 99)
throw Exit(); /* leave loop */
cout < < "you entered: " < < num;
cout < < "enter another number " < <endl;
}
}
catch (Exit & e)
{
cout < < "game over" < < endl;
}
}

This code is inefficient because of the performance overhead of
exception handling. Furthermore, it's verbose and less readable. The
use of ordinary for-loops and break statements rather than exception
handling would make this code more efficient and readable.

As a rule, use exceptions to report and handle run-time errors
exclusively.


----------------------------------------------

OVERLOADING OPERATORS FOR ENUM TYPES

For some enum types, it may be useful to overload operators such as ++
and -- that can iterate through the enumerator values. You can do it
like this:

enum Days {Mon, Tue, Wed, Thur, Fri, Sat, Sun};

Days & operator++(Days & d, int) /* int indicates postfix ++ */
{
if (d == Sun)
return d = Mon; /* rollover */
int temp = d;
return d = static_cast < Days > (++temp);
}

int main()
{
Days day = Mon;
for (;;) /* display days as int's */
{
cout < < day < < endl;
day++;
if (day == Mon)
break;
}
}


----------------------------------------------

FORWARD-DECLARING I/O CLASSES AND TEMPLATES

The standard header < iosfwd > contains forward declarations of the
standard I/O classes and templates. This header is sufficient to refer
to any of the I/O classes and templates, but not to apply operation to
them. For example:

#include < iosfwd >
using namespace std;
class C
{
public:
friend ostream & operator < < (ostream & os, const C & d);
};
ostream & operator < < (ostream & os, const C & d);

In the example, the declaration of the friend function does not need a
complete definition of the ostream class; a forward declaration is
sufficient in this case. Therefore, < iosfwd > is #included instead of
the full-blown < iostream >. The result is a significantly reduced
compilation time.


----------------------------------------------

OVERLOADING NEW AND DELETE FOR A CLASS

It is possible to override the operators new and delete and define a
specialized version of them for a given class. Thus, for a class C
that defines these operators, the statements

C* p = new C;
delete p;

invoke the class's versions of new and delete, respectively. Defining
class-specific versions of new and delete is useful when the default
memory management scheme is unsuitable, or for debugging purposes.

In the following example, class C overloads operator new to alter the
default behavior in the event of an allocation failure; instead of
throwing std::bad_alloc, this specific version throws const char *. A
matching operator delete is also defined:

#include < cstdlib > /* malloc() and free() */
#include < iostream >
using namespace std;
class C
{
public:
void* operator new (size_t size);
void operator delete (void *p);
};

void* C::operator new (size_t size) throw (const char *)
{
void * p = malloc(size);
if (p == 0)
throw "allocation failure"; /* instead of std::bad_alloc */
return p;
}

void C::operator delete (void *p)
{
free(p);
}

Note that overloaded new and delete are implicitly declared static.
Remember also that a user-defined new implicitly invokes the object's
constructor; likewise, a user-defined delete implicitly invokes the
object's destructor.


----------------------------------------------

SOME QUIET DIFFERENCES BETWEEN C AND C++

There are a few semantic differences between C and C++ in the
interpretation of certain language constructs. These differences may
not result in a compiler diagnostic, and therefore, it is important to
pay attention to them when porting code from a C compiler to a C++
compiler, and vice versa.

In C, the size of an enum type is the same as the size of an int. In
C++, there is no such guarantee: The size of an enum may be larger or
smaller than the size of an int.

In C, the result of applying operator sizeof to a character constant,
as in the expression sizeof('c'), is equivalent to sizeof(int). In
C++, on the other hand, the expression sizeof('c') is equivalent to
sizeof(char).


----------------------------------------------

STRING LITERALS AND TEMPLATE ARGUMENTS

A template can take the address of an object with external linkage as
an argument. Consequently, you cannot use a string literal as a
template argument since string literals have internal linkage. For the
same reason, you cannot use local pointers for that purpose, either.
For example:

template < class T, const char * > class A
{/*...*/};
extern const char *global_ptr;
void array_user()
{
const char * p ="illegal";
A < int, "invalid" > aa; /* error, string literal used as argument
*/
A < int, p > ab; /* error, p has internal linkage */
A < int, global_ptr > ac; /* OK, global_ptr has external linkage */
}


----------------------------------------------

WHAT ARE LVALUES AND RVALUES?

An object is a contiguous region of memory storage. An lvalue
(pronounced L value) is an expression that refers to such an object
(the original definition of lvalue referred to "an object that can
appear on the left-hand side of an assignment"). An expression that
can appear on the right-hand side of an expression (but not on its
left-hand side) is an rvalue. For example:

#include < string >
using namespace std;
int & f();
void func()
{
int n;
char buf[3];
n = 5; /* n is an lvalue; 5 is an rvalue */
buf[0] = 'a'; /* buf[0] is an lvalue, 'a' is an rvalue */
string s1 = "a", s2 = "b", s3 = "c"; /* "a", "b", "c" are rvalues
*/
s1 = /* lvalue */
s2 +s3;
/* s2 and s3 are lvalues that are implicitly converted to
rvalues */
s1 =
string("z"); /* temporaries are rvalues */
int * p = new int; /* p is an lvalue; 'new int' is an rvalue */
f() = 0; /* a function call that returns a reference is an lvalue
*/
s1.size(); /* otherwise, a function call is an rvalue expression
*/
}

An lvalue can appear in a context that requires an rvalue; in this
case, the lvalue is implicitly converted to an rvalue. However, an
rvalue cannot be converted to an lvalue. Therefore, it is possible to
use every lvalue expression in the example as an rvalue, but not vice
versa.


----------------------------------------------

THE "BIG THREE RULE" VERSUS THE "BIG TWO RULE"

The famous "Big Three Rule" says that if a class needs any of the Big
Three member functions (copy constructor, assignment operator, and
destructor), it needs all of them. In general, this rule refers to
classes that allocate memory from the free store. However, many other
classes require only that the Big Two (copy constructor and assignment
operator) be defined by the user; the destructor, nonetheless, is not
always required. Examine the following example:

class Year
{
private:
int y;
bool cached; /* has this instance been cached? */
public:
Year(int y);
Year(const Year & other) /* make sure 'cached' isn't copied */
{
y = other.getYear();
}
Year & operator =(const Year & other) /* make sure 'cached' isn't
copied */
{
y = other.getYear();
return *this;
}
int getYear() const { return y; }
}; /* no destructor is required for class Year */

Class Year does not allocate memory from the free store, nor does it
acquire any other resources during its construction. A destructor is
therefore unnecessary. However, the class needs a user-defined copy
constructor and assignment operator to ensure that value of the member
'cached' is not copied, because it is calculated for every individual
object separately.


----------------------------------------------

TWO FORMS OF DYNAMIC_CAST

The operator dynamic_cast comes in two flavors. One uses pointers and
the other uses references. Accordingly, dynamic_cast returns a pointer
or a reference of the desired type when it succeeds. However, when
dynamic_cast cannot perform the cast, it returns a null pointer, or in
case of a reference, it throws a std::bad_cast exception:

void f(Shape & shape)
{
Circle * p = dynamic_cast < Circle * > ( & shape); /* is shape a
Circle? */
if ( p )
{
p->fillArea ();
}
else {} /* shape is not Circle */
}

In this example, dynamic_cast examines the dynamic type of Shape by
casting its address to a pointer to Circle. If the cast is successful,
the resultant pointer is used as a pointer to Circle.

In contrast, the next example uses a reference dynamic_cast. You
should always place a reference dynamic_cast within a try-block and
include a suitable catch-statement to handle the potential
std::bad_cast exception, as follows:

#include < typeinfo > /* for std::bad_cast */
void f(Shape & shape)
try
{
Circle & ref = dynamic_cast < Circle & > (shape);
ref.fillArea(); /* successful cast */
}
catch (std::bad_cast & bc) /* shape is not a Circle */
{/*..*/}
}


----------------------------------------------

DEFINING A STATIC MEMBER FUNCTION

A static member function in a class can access only other static
members of its class. Unlike ordinary member functions, a static
member function can be invoked even when no object instance exists:

class Clock
{
public:
static void showTime();
};

int main()
{
stat:: showTime(); /* called without an object */
stat s;
s. showTime(); /* called from an object */
}

Static members are used when all other data members of an object are
also static, when the static function does not depend on any other
object member, or, simply, when a global function is undesirable (in
the pre-namespaces era, this was the predominant method of preventing
global functions).


----------------------------------------------

CALLING A MEMBER FUNCTION FROM A CONSTRUCTOR

You can safely call member functions--virtual and nonvirtual alike--of
an object from its constructor. It is guaranteed that the invoked
virtual is the one defined for the object whose constructor is
executing. Note, however, that virtual member functions of an object
derived from the one whose constructor is executing are not invoked:

class A
{
public:
virtual void f();
virtual void g();
};
class B: public A
{
public:
void f (); /* overrides A::f() */
B()
{
f(); /* B::f() */
g(); /* A::g()*/
}
};
class C
{
public:
void f (); /* overrides B:::f() */
};
B * p = new B;

B's constructor calls f() and g(). The calls are resolved to B::f()
and A::f(), respectively, because B overrides f() but it doesn't
override g(). Note that C::f() is not called from B's constructor.


----------------------------------------------

BOOSTING PERFORMANCE OF LEGACY SOFTWARE

When you port pure C code to a C++ compiler, you may discover slight
performance degradation. This is not a fault in the programming
language or the compiler, but a matter of compiler setting. To regain
the same (or better) performance as with a C compiler, simply disable
the compiler's RTTI and exception-handling support.

Why is this? To support these features, a C++ compiler appends
additional "scaffolding" code to the program. Consequently, the
program's speed may be reduced and its size may increase. The
additional overhead incurred by RTTI and exception handling ranges
from five percent to 20 percent, depending on the compiler. When
you're using pure C, this overhead is unnecessary, so you can safely
disable RTTI and exception-handling support. However, you should not
attempt to apply this tweak to C++ code or C code that uses any C++
constructs, such as operator new.


----------------------------------------------

VIRTUAL FUNCTIONS SHOULD NOT BE DECLARED PRIVATE

It is customary to extend virtual functions in a derived class by
first invoking the base's version of that function and then extending
it with additional functionality. Therefore, there is little point in
declaring a virtual member function private--by doing so, you disable
the user's ability to extend it in a derive class.


----------------------------------------------

NAMESPACES DO NOT IMPOSE ANY OVERHEAD

The use of namespaces in your application does not incur runtime or
memory overhead. Therefore, you can use namespaces in embedded systems
and time-critical applications without hesitation.


----------------------------------------------

EFFICIENCY OF STRING COMPARISONS

Class std::string defines three versions of the overloaded ==
operator:

bool operator == (const string & l, const string & r);
bool operator == (const char* l, const string & r);
bool operator == (const string & l, const char* r);

This proliferation may seem redundant, since std::string has a
constructor that automatically converts a const char * to a string
object. Thus, one could make do with only the first version of
operator ==, which in turn converts a char * to a temporary string
object, and then performs the comparison. However, the overhead of
creating a temporary string can be unacceptable under some
circumstances.

The C++ Standardization Committee's intent was to make comparison
operation of std::string as efficient as possible. Therefore, the
additional overloaded versions were added to allow direct comparisons,
without the additional overhead of temporaries.


----------------------------------------------

DYNAMIC_CAST AND ACCESS SPECIFICATIONS

Operator dynamic_cast fails when it cannot convert its argument to the
desired type. Usually, such a failure results from an attempt to cast
an object to a nonrelated class. However, the int may also be the
result of an attempt to cast a derived object to a private base class.
For example:

class Container {/*..*/ };
class Allocator{/*..*/};
class Stack: public Container, private Allocator
{/*..*/ };

int main()
{
Stack s;
/* attempt to cast a pointer to Stack to a pointer to Allocator */
Allocator* p =
dynamic_cast < Allocator* > ( & s); /* runtime failure */
}

In the example above, dynamic_cast fails because Stack is not publicly
derived from Allocator.


----------------------------------------------

PREFER OBJECT INITIALIZATION TO ASSIGNMENT

In general, initializing an object is more efficient than assignment.
Consider the following code snippet:

string s1 = "Hello ";
string s2 = "World";
string both;
both = s1+s2; /* assignment rather than initialization; inefficient
*/

The compiler transforms the statement both = s1+s2; into this:

string temp (s1 + s2); /* store result in a temporary */
both = temp; /* assign */
temp.::~string(); /* destroy temp */

Using initialization rather than assignment is more efficient:

string both = s1+s2; /* initialization */

This time, the compiler transforms the expression both = s1+s2; into
this:

string both (s1 + s2);

In other words, the construction and destruction of a temporary are
avoided in this case.

To conclude, object assignment requires the creation and destruction
of a temporary, whereas initialization does not. Whenever you have the
choice, choose object initialization rather than assignment.


----------------------------------------------

FORWARD DECLARATIONS AND TYPEDEF NAMES

It is illegal to use forward declarations with typedef names:

class string; /* illegal, string is a typedef name */
void f(string & s);

Even a typename won't do here:

typename std::string; /* still illegal */
void f(std::string& s);

Can you see what the problem is with these forward declarations?
std::string is not a class, but a typedef name defined in the standard
header < string > like this:

/* note: this is a simplified form */
typedef basic_string <char, char_traits < char >, allocator < char >
>string;

These forward declarations don't provide the necessary information
about the type of std::string. Yet, to generate the correct mangled
name for the function f(), the compiler has to see the non-typedef'd
form of its argument. For this reason, you cannot use a forward
declaration of typedef'd names. There is no escape from including the
header < string > in this case.


----------------------------------------------

THE ROLE OF ALLOCATORS

Every STL container uses an allocator. Allocators encapsulate the
low-level details of the memory model of a given platform: They hide
the size of pointers, reallocation policy, block and page size, heap
location, etc. Containers are therefore rather portable, because a
different allocator type can be plugged to them, depending on the
platform. Your STL implementation provides an allocator that is
suitable for your platform and application's needs. However, some
applications may require custom-made allocators.


----------------------------------------------

REPORTING AN ERROR DURING OBJECT CONSTRUCTION

Constructors have no return value (not even void). Therefore, you have
to use alternative techniques of reporting errors during object
construction. There are three common techniques of reporting such
errors.

The first technique uses C-style error handling: When an error occurs
during construction, the constructor assigns an error value to a
global variable, which is later examined. Although this is the least
preferable technique, it can be useful when combining legacy code
written in C with new C++ code:

int errcode = OK; /* global flag */
Date::Date(const char *datestr)
{
if (isValid(datestr) == false)
errcode = EINVALIDDATE;
...
}
int main()
{
Date d("02/29/1999"); /* invalid */
if (errcode == OK) /* proceed normally */
{
...
}
else {..} /* handle the error */
}

Date's constructor turns on the global flag errcode to indicate a
runtime error. Main() subsequently checks the flag's value and handles
the exception.


----------------------------------------------

USING PARAMETERS TO REPORT AN ERROR DURING OBJECT'S CONSTRUCTION

Using global variables to indicate runtime errors has many
disadvantages. An alternative method of reporting runtime errors
during object construction is to pass an additional argument to the
constructor. The constructor in turn assigns an appropriate value to
that argument that indicates success or failure. For example:

Date::Date(const char *datestr, int & status)
{
if (isValid(datestr) == flase)
status = EINVALIDDATE;
...
}
int main()
{
int status;
Date d("02/29/1999", status);
if (status == OK) /* proceed normally */
{
...
}
}


----------------------------------------------

THROWING EXCEPTIONS TO REPORT AN ERROR DURING OBJECT CONSTRUCTION

Many implementations still don't support exception handling very well.
Therefore, the methods of using global variables and passing a
parameter to the constructor can be used instead. However, on
implementations that support exception handling, throwing an exception
is the preferred method of reporting runtime errors during
construction. The advantages of exceptions are better support for
object-oriented programming, simpler and more readable code, safety,
and automation. The constructor in the following example throws an
exception to report a runtime error:

class X {
public:
X(const char* description);
...
};

Date::Date(const char *datestr) throw (X)
{
if (isValid(datestr) == flase)
throw X("invalid date string");
...
}

int main()
{
try
{
Date d("02/29/1999");
// ... proceed normally
}
catch (X & exception)
{
/* ... handle the exception */
}
}


----------------------------------------------

FUNCTION ARGUMENTS EVALUATION ORDER

The order of evaluation of function arguments is unspecified. This
means that for a function that takes the arguments (a, b, c), any
permutation of this set is a valid argument evaluation sequence. To
demonstrate that, suppose you write a function f() and you call it
like this:

bool x; /* global */
f( g(false), 1, x, 3);

Suppose also that g() changes the value of x. One cannot tell which
value of x is passed to f() as its third argument--it could be either
the value of x before or after the call to g(false). Therefore,
writing code that relies on a specific evaluation order of function
arguments is bad programming practice and should be avoided.


----------------------------------------------

THE NEW LONG LONG DATA TYPE

The long long type denotes an integral data type that is at least as
large as a long int. On 32-bit architectures and 64-bit architectures,
long long occupies 64 bits (eight bytes). Although long long is not
defined by the ANSI/ISO C++ standard yet, it is defined by the C9X
draft standard and, in practice, many platforms and compilers already
support it. As with other integral types, a long long has an unsigned
counterpart:

unsigned long long distance_to_star;

The suffix ll or LL can be added to a literal integer to indicate a
long long type:

const long long year_light_in_km = 9460800000000LL;

Likewise, the suffixes ull and ULL can be added to a literal integer
to indicate an unsigned long long type.


----------------------------------------------

STATIC CLASS MEMBERS MAY NOT BE INITIALIZED IN A CONSTRUCTOR

You cannot initialize static data members inside the constructor or a
member-initialization list:

class File
{
private:
static bool locked;
public:
File::File(): locked(false) {} /*error*/
};

Although compilers flag such ill-formed initializations as errors,
programmers often wonder why this is an error. Remember that a
constructor is called as many times as the number of objects created,
whereas a static data member may be initialized only once, since it is
shared by all the class objects. Therefore, static member
initialization should be done outside the class, as in this example:

class File
{/*..*/};

File::locked = false; /* OK */

Alternatively, for const static members of an integral type, the
Standard now allows in-class initialization as follows (note that some
compilers don't support this feature yet):

class Screen
{
private:
const static int pixels = 768*1024; /* OK */
public:
Screen() {/*..*/}
...
};


----------------------------------------------

ALTERNATIVE STL IMPLEMENTATION

The STLport organization offers an alternative implementation of the
Standard Template Library that you can install instead of the original
STL implementation supplied with your compiler. The creators of this
implementation claim that it's fully compatible with Microsoft's
Visual C++ 5.0 and 6.0, as well as many other platforms. You can
download the STLport implementation from

http://www.stlport.org/index.shtml

The site contains installation directions and extensive documentation
about the STLport version of STL.


----------------------------------------------

RETURNING A VOID EXPRESSION FROM A VOID FUNCTION

Examine the following code snippet. Does your compiler accept it?

void a_func_returning_void();
void func()
{
return a_func_returning_void(); /* returning a value from a void
function? */
}

At present, most compilers will not accept this code, although it's
perfectly legal. The problem is that until not long ago, returning an
expression from a function that returns void was an error, even if the
returned expression itself was void. This restriction was changed
recently: A function with a void return type may now return an
expression that evaluates to void. This change was required to enable
better support of generic algorithms in the STL.


----------------------------------------------

CATCH EXCEPTIONS BY REFERENCE

Although you can catch an exception by value, as in

catch( Exception except) /* by value */
{
/* ... */
}

it's better to catch an exception by reference:

catch( Exception & except) /* by reference */
{
/* ... */
}

Catching an exception by reference has several advantages. First, pass
by reference is more efficient, since it avoids unnecessary copying of
objects. Second, you avoid slicing of derived exception objects.
Finally, you ensure that modifications applied to the exception within
a catch-clause are preserved in the exception object when it is
rethrown.


----------------------------------------------

THE CC++ PARALLEL PROGRAMMING LANGUAGE

CC++ (this is not a typo) is a parallel programming language based on
C++. A free CC++ compiler as well as an overview of CC++ features,
syntax, and supported platforms can be found at

http://globus.isi.edu/ccpp/

Although CC++ is not standardized yet, it's interesting to examine the
elegance of its straightforward and relatively simple handling of
parallel processing, resource locking and unlocking mechanisms, and
its platform-neutrality.


----------------------------------------------

DATA AGGREGATES

The term "aggregate" refers to an array or a class with no
constructors, no private or protected data members, no base classes,
and no virtual functions. In other words, an aggregate is a POD (Plain
Old Data) object or an array thereof.

Aggregates differ from class objects and arrays of class objects in
several ways. In particular, you can initialize every aggregate of
every type and size by the '={0};' initializer list. This initializer
list guarantees that the entire aggregate is zero-initialized:

struct Employee
{
int ID;
int rank
char name[12];
int salary;
};

/* using the '={0}' initializer to initialize aggregates */
Employee emp = {0}; /* all emp members are zero-initialized */
Employee emp_arr[10] = {0}; /* all array elements are zero-initialized
*/

Note that nonaggregate objects and arrays cannot be initialized this
way; they are initialized by a constructor.


----------------------------------------------

THE C++ EXCEPTION HANDLING MODEL

In a resumptive model, after an exception has been handled, the
program continues execution at the point where the exception was
thrown. The C++ exception-handling model is nonresumptive,
though--program execution resumes at the next statement following the
catch-block. This is a source of confusion among C++ novices, who
mistakenly assume that the program automatically returns to the point
where an exception was thrown from, but it doesn't.


----------------------------------------------

GET A FREE COPY OF THE GNU C/C++ COMPILER 2.95

The GNU project focuses on the development and distribution of open
source software. The newly released GCC 2.95 is the latest version of
GNU C/C++ compiler. Release 2.95 includes numerous bug fixes, new
optimizations, and support for new platforms. In addition, its
standard compliance has been improved. You can download GCC 2.95 from
the GNU site at

http://www.gnu.org/software/gcc/gcc-2.95/gcc-2.95.2.html


----------------------------------------------

WHY THE COPY CONSTRUCTOR'S PARAMETER MUST BE PASSED BY REFERENCE

The canonical form of a copy constructor of a class called C is

C::C(const C & rhs);

You can also declare the copy constructor's parameter as follows:

C::C(C & rhs); /*non-const, OK*/

That is, the const specifier is not mandatory. Note, however, that the
parameter may not be passed by value:

C::C(const C rhs); /* Error */

Can you see why the parameter must be passed by reference? Whenever
you pass an object by value, you implicitly invoke its copy
constructor (because the object being passed by value is in fact a
copy of the original argument). Attempting to pass a copy
constructor's argument by value will result in an infinite recursion:
The copy constructor calls another copy constructor, which in turn
calls another copy constructor, and so on. To avoid this, C++ requires
that a copy constructor's parameter be passed by reference.


----------------------------------------------

 

A COMMON MISTAKE IN TEMPLATE INSTANTIATION

The type that a template takes as an argument must have external
linkage. In other words, you cannot instantiate a template with a
locally declared type:

int main()
{
struct S
{
int current_state;
char description[10];
};
queue < S > qs; /* 1. compilation error */
}

The line numbered 1 causes a compilation error because it attempts to
instantiate a template with a locally declared type, S. S has no
linkage, and as such, it cannot serve as a valid template argument. To
fix this, you should move the declaration of struct S outside of
main():

struct S
{
/* ... */
};

int main()
{
queue < S > qs; /* now OK */
}


----------------------------------------------

A FUNCTION TEMPLATE CANNOT BE VIRTUAL

A reader tried to declare a template as a virtual member function of a
class. However, his compiler refused to compile the code. He wanted to
know whether it was a compiler bug:

template &lt class T &gt struct A
{
template &lt class C &gt
virtual void f(C); /* illegal, virtual template function */
virtual void g(); /* OK, g is not a template */
};

It's not a compiler bug. The C++ Standard says (14.5.2 p 3): "A member
function template shall not be virtual."



----------------------------------------------

ADD A CATCH(...) BLOCK TO YOUR EXCEPTION HANDLERS HIERARCHY

If your application uses exception handling, it is recommended that
you always add a catch(...) statement after all the existing catch
blocks. Remember that even if your code contains the appropriate
handlers for expected exception, other unexpected exceptions may still
be thrown by standard functions, algorithms, and containers. By adding
a catch(...) statement to the end of existing handlers, you guarantee
that no exception is left unhandled. Furthermore, a catch(...)
statement ensures that the program properly destroys all automatic and
static objects as part of the stack unwinding process, even if an
unexpected exception is thrown. For example:

int main()
{
try
{
func();
}
catch(DerivedEx & de) /*most derived*/
{}
catch(Ex & exc)
{}
catch(...) /*catch all unexpected exceptions*/
{}
}

Note that a catch(...) block cannot detect the type of the exception
it handles. Therefore, it should perform only general cleanup and
logging operations.


----------------------------------------------

APPLYING OPERATOR TYPEID TO NONPOLYMORPHIC OBJECTS

Operator typeid retrieves the runtime type information of a
polymorphic object (an object having at least one virtual member
function). Using typeid to retrieve the type information of
nonpolymorphic objects and fundamental types is also legal. However,
in this case typeid returns the static type of the object rather than
its dynamic type. For example:

#include < typeinfo >
#include < iostream >
using namespace std;
class B{};
class D: public B{};

void f()
{
typedef int I;
B* p = new D;
cout << typeid(I).name(); /*display 'int' */
cout << typeid(*p).name(); /* display 'B' rather than 'D'*/
}

Adding a virtual member function to B will force the typeid operator
to access the dynamic type of its argument. Consequently, the
following line will display D rather than B:

cout << typeid(*p).name();

Note that applying dynamic_cast to fundamental types or nonpolymorphic
classes is an error.


----------------------------------------------

AUTO_PTR SHOULD NOT HOLD ARRAYS OF OBJECTS

The auto_ptr class template automatically destroys an object that is
allocated on the free store when the current scope is exited. For
example:

#include < memory > /* for auto_ptr */
#include < string >
using namespace std;
int main()
{
auto_ptr < string > ps (new string);
/* .. use ps */
} /* ps is automatically deleted */

Note, however, that binding auto_ptr to an array of objects results in
undefined behavior, because auto_ptr's destructor calls scalar delete;
it never calls delete [] to destroy its bound object. Therefore, never
bind auto_ptr to an array:

auto_ptr < string > ps (new string[2]); /* undefined behavior */


----------------------------------------------

AVOID TYPEDEF'S IN STRUCTS DEFINITIONS

In olden days, before C++ appeared, it was customary to declare a
struct as a typedef:

typedef struct DATE_TAG
{
int day;
int month;
int year;
} Date; /* 'Date' is a typedef */

This way, one could create an instance of the struct without having to
use the keyword 'struct':

/* C code */
Date date; /* 'struct' not required */
struct DATE_TAG another_date;

In C++, the use of a typedef in this context is unnecessary because
you don't need the elaborated type specifier (for example, struct,
union, and class) to create an instance:

// C++ code
DATE_TAG another_date;

Therefore, avoid declaring structs as typedef names in C++ code.


----------------------------------------------

AVOIDING RECURRENT POINTER DELETION

The result of applying delete to the pointer more than once is
undefined:

std::string ps = new std::string;
delete ps;
// ..many code lines
delete ps; /* undefined behavior, ps deleted twice*/

Of course, the solution to this bug is to avoid deleting the same
pointer twice. However, if code modifications are impossible--for
example, when the bug exists in a software library that is maintained
by a third party--a temporary workaround to this problem is to assign
a null value to a pointer right after it has been deleted. C++
guarantees that a null pointer deletion is harmless:

std::string ps = new std::string;
delete ps;
ps = NULL;
// ..many code lines
delete ps; /* harmless*/


----------------------------------------------

BEGINNING C++

One of the most frequently asked questions I receive is, "I've done
some VB/Perl/PHP programming before. How should I begin learning C++?"
First, remember that C is not a prerequisite for learning C++. In
fact, as a beginner, you may find that learning C++ directly is
easier. Programming courses are costly. You can find less expensive
alternatives. First, get a good C++ primer book. Since this is a
matter of personal taste, I cannot recommend a specific title. Some
folks prefer Q&A format, while others are more comfortable with a
structured organization of topics, so you should find the book that
suits your preferences. Second, you need a C++ compiler to experiment
with (there are plenty of free C++ compilers on the Web).

A good primer book and a compiler can provide you with all the
theoretical and practical training you need to learn C++ from scratch
when time is the only resource you have to invest.


----------------------------------------------

DEFAULT PARAMETER VALUES ARE NOT PART OF A FUNCTION'S TYPE

Although the default parameter values of a function must appear in its
declaration, they are not considered part of its type. Therefore, you
cannot overload a function by using different default parameter
values:

void f(int n = 6);
void f(int n = 0); /*error, redefinition of f()*/

The attempt to overload f() is illegal because the compiler considers
both versions of f() identical.


----------------------------------------------

DEFINING A FUNCTION IN AN UNNAMED NAMESPACE

The use of the keyword "static" to limit the linkage of classes and
functions to a translation unit is considered deprecated. Unnamed
namespaces are the recommended alternative. However, avoid a common
mistake of declaring a function in an unnamed namespace and defining
it later, somewhere outside that unnamed namespace:

namespace
{
void func(); /* only a declaration */
}

void func() /* most likely an error */
{
// ...
}

The programmer's intention was to define the function that was
previously declared in the unnamed namespace. However, the compiler
cannot guess the programmer's intention because the definition of
func() looks exactly like an ordinary extern function. Therefore, the
compiler treats the two instances of func() as referring to two
distinct functions: One appears in the unnamed namespace and the other
in the global namespace. To fix this, func() must be defined inside
unnamed namespace, too:

namespace
{
void func() // OK
{
// ...
}
}


----------------------------------------------

DYNAMIC BINDING AND STATIC BINDING

Not every call to a virtual function is resolved dynamically. For
example:

class A
{
public:
virtual void func() {/*..*/}
};
int main()
{
A a;
A.func(); /* resolved at compile time */
}

Dynamic binding applies in two cases: when calling a virtual function
through a pointer to a polymorphic object, and when calling a virtual
function through a reference to a polymorphic object (a polymorphic
object is one that declares or inherits at least one virtual member
function):

void f(A & ref, A* pa)
{
ref.func(); /* resolved dynamically */
pa->func(); /* ditto */
}
int main()
{
A a;
f(a, & a);
}

You can bypass the dynamic binding mechanism by using the member
function's fully qualified name:

pa->A::func(); /* always resolved statically */


----------------------------------------------

FORWARD-DECLARING A NAMESPACE MEMBER

Suppose you need to forward-declare class Interest, which is a member
of namespace Bank. Seemingly, all you need to do is use the class's
fully qualified name:

class Bank::Interest;
void Analyze(Bank::Interest & in); /*error*/

Alas, the qualified name Bank::Interest will cause a compilation error
because the compiler doesn't know that Bank is a namespace. The
solution is simple: you need to open the class's namespace,
forward-declare the class in that namespace, and immediately close
that namespace:

namespace Bank
{
class Interest; /*fwd declaring class Interest,
which is a member of namespace Bank*/
}
void Analyze(Bank::Interest & in); /*OK*/

The example above reopens namespace Bank (which is defined in another
source file already). It then forward-declares class Interest inside
that namespace and immediately closes it. Remember that a
forward-declaration is just a declaration, so it doesn't cause any
link errors about "duplicate symbols."


----------------------------------------------

FUNCTION SIGNATURES

A function's signature provides the information needed to perform the
overload resolution. The signature consists of the function's
parameter list and their ordering. Note that a function's return type
is not considered part of its signature, and it does not participate
in overload resolution.


----------------------------------------------

GETTING ALONG WITHOUT NULL REFERENCES

In C, algorithms such as bsearch() and lfind() return a null pointer
to indicate that the sought-after element wasn't found. Unlike a
pointer, a reference must always be bound to a valid object.
Therefore, a reference can never be null. I've often seen the
following dreadful hack as an attempt to fabricate a null reference:

int & locate(int * pi, int size, int value)
{
if (find_int(pi, size, value) != NULL)
// ...
else
return *((int *) (0)); /* very bad */
}

The return statement fakes a null reference by binding a reference to
a dereferenced null pointer. This hack violates two C++ rules: The
function locate() returns a reference to a local automatic variable.
The results of using such a reference are undefined (this is called a
"dangling reference"). Worse yet, even if locate() used a
non-automatic variable to avoid the dangling reference problem, any
attempt to use the returned reference would still cause a runtime
crash because you cannot dereference a null pointer. Unfortunately,
many compilers will accept this code, letting the user detect its
disastrous results at runtime.

You can resolve the lack of null references with several approaches.
Throwing an exception is one of them. However, the overhead and
complexity of exception handling may be overkill for simple functions
such as locate(). In this case, you can return a reference to a
special object (such as a negative integer when an array subscript
cannot be found):

int & locate(int * pi, int size, int value)
{
static int unfound = -1;
if (find_int(pi, size, value) != NULL)
// ...
else
return unfound; /* fine */
}


----------------------------------------------

HELPING THE COMPILER DEDUCE THE TYPE OF A TEMPLATE ARGUMENT

In general, the compiler deduces the type of a function template
argument automatically. For example:

int n = max (5,10); /* max< int > deduced because 5 and 10 are int's
*/

However, sometimes the compiler needs more explicit clues regarding
the type of the argument, as in the following example:

template < class T >T f()
{
return static_cast< T > (0);
}
int main()
{
int n = f();/*error, can't deduce argument type*/
double d = f(); /*ditto*/
}

The program fails to compile because the compiler cannot deduce what
the type of f's argument is (the return type is insufficient for that
purpose). In situations like these, you can state the type of the
template argument explicitly:

int i = f < int > (); /*now OK */
double d = f < double >(); /* OK*/


----------------------------------------------

INITIALIZE CLASS'S DATA MEMBERS EASILY

Suppose you want to initialize all the data members of class Book
inside its constructor:

class Book
{
private:
char ISBN[11];
int year
char name[100];
public:
virtual ~Book();
Book();
};

One way to do that is by assigning a value to every member inside the
constructor body. However, this is tedious. Don't be tempted to use
memset() to initialize the members:

Book::Book()
{
memset(this, 0, sizeof (*this)); /* very bad idea */
}

memset() also initializes the object's virtual table pointer.
Consequently, the program will crash at runtime. Instead, remove all
the class's data members to a dumb struct that doesn't have any
virtual member functions and add a constructor to the struct:

struct BookData
{
char ISBN[11];
/*... rest of the data members */
BookData() {memset(this, 0, sizeof (*this)); }
/* now OK, struct has no vptr */
};

Next, derive Book from that struct using private inheritance:

class Book : private BookData {...};

And that's all. Now every data member of Book is automatically
initialized by BookData's constructor.


----------------------------------------------

INITIALIZING ARRAYS IN A CLASS

You cannot initialize an array class member by a member-initialization
list. For example:

class A
{
private:
char buff[100];
public:
A::A() : buff("") /* error */
{}
};

The following forms won't compile either:

A::A() : buff('\0') {} /* ill-formed */
A::A() : buff(NULL) {} /* ill-formed */

Instead, you should initialize arrays inside the constructor body:

A::A()
{
memset(buff, '\0', sizeof(buff));
}


----------------------------------------------

INLINE OR __FORCEINLINE

The decision whether a function declared inline is actually
inline-expanded is left to the sole discretion of the compiler. In
other words, inline is only a recommendation. The compiler may refuse
to inline a function that has loops, for example. Visual C++ and
several other compilers offer nonstandard keywords that control the
inline expansion of a function, in addition to the standard inline
keyword.

What are the uses of the nonstandard keywords? The nonstandard keyword
__forceinline overrides the compiler's heuristics and forces it to
inline a function that it would normally refuse to. I'm not sure I can
think of a good reason to use __forceinline, since it may cause a
bloated executable file and a reduced instruction-cache hit.
Furthermore, under extreme conditions, the compiler may not respect
the __forceinline request, either. So, in general, you should stick to
good old inline, because it ensures that your code is more portable,
and it lets the compiler "do the right thing." You should use
__forceinline as a last resort.


----------------------------------------------

MAKING A CLASS TEMPLATE A FRIEND OF ANOTHER CLASS TEMPLATE

A class template can be a friend of another class template. For
example:

template < class U > class D{/*...*/};
template < class T > class Vector
{
public:
// ..
template < class U > friend class D;
};

In this example, every specialization (instantiation) of the class
template D is a friend of every specialization of the class template
Vector.


----------------------------------------------

OPEN MODE FLAGS

In older stages of C++, the ios::nocreate and ios::noreplace open
modes were part of the < fsteam.h > family of classes. However, the
revised < fstream > family of file access classes, which should be
used instead of the now deprecated < fstream.h >, does not recognize
these flags anymore. What happened to them?

Along with the templatization of < fstream >, the C++ standardization
committee decided to remove from std::ios_base (which now replaces
ios) the open mode flags that were considered too platform-specific,
including ios_base::nocreate and ios_base::noreplace. Programmers who
migrate from < fstream.h > to < fstream > may find that their code
refuses to compile because it contains these disused open mode flags.
The alternative is to use either platform-specific APIs or alternative
coding conventions that do not rely on these flags.


----------------------------------------------

OPTIMIZATIONS THAT ARE CARRIED OUT BY THE LINKER

While most of the code optimizations are performed at compile time,
there are optimizations that only a linker can perform. For example,
it can detect unreferenced function calls. These are functions that
exist in one or more source files and were compiled. If the linker
detects that the application never calls these functions, it can
remove their text (binary code) from the resultant executable, thereby
producing a smaller and faster program.

With some linkers, you can control the number of passes the linker
makes. The more passes you enable, the more unreferenced functions are
removed, though the cost is a longer linkage time.


----------------------------------------------

OPTIMIZING LOOPS

Consider the following code:

class Foo{/*..*/};
std::vector < Foo > v;

for (int j =0; j < MAX; j++)
{
v.push_back ( Foo() ); /* using a temporary */
}

This for loop is very inefficient: It constructs and destroys a
temporary Foo object on every iteration. You can easily avoid the
unnecessary overhead of constructing and destroying this object if you
declare it outside the loop:

Foo something; // avoiding a temporary
for (int j =0; j < MAX; j++)
{
v.push_back ( something );
}

This improved version constructs and destroys the object only once,
regardless of how many times the loop executes.

In general, you should avoid declaring temporary objects inside loops.
Instead, create them outside the loop.


----------------------------------------------

OVERLOADED SUBSCRIPT OPERATOR SHOULD RETURN A REFERENCE

When you overload the operator [], remember that its non-const version
should return an object by reference, not by value. Otherwise, you
won't be able to use it in assignment expressions. For example:

class IntArray
{
private:
/*...*/
public:
int & operator [] (int index); /*return a reference */
};

IntaArray iar;
iar[0] = 1; /*to make this work, operator [] must return int & */


----------------------------------------------

OVERLOADING OPERATOR + PROPERLY

The built-in operator + takes two operands of the same type and
returns their sum without changing their values. In addition, + is a
commutative operator: You can swap the operands' positions and get the
same result. Therefore, an overloaded version of operator + should
reflect all these characteristics.

You can either declare operator + as a member function of its class or
as a friend function:

class Date
{
public:
Date operator +(const Date & other); /* member */
};
class Year
{
friend Year operator+ (const Year y1, const Year y2); /* friend */
};

The friend version is preferred because it reflects symmetry between
the two operands. Because built-in + does not modify any of its
operands, the parameters of the overloaded + are const. Finally,
overloaded + should return the result of its operation by value, not
by reference.


----------------------------------------------

PACK A LONG PARAMETER LIST IN A STRUCT

A function having a long list of parameters, as in

void retrieve(const string & title,
const string & author,
int ISBN, int year,
bool & inStore);

often becomes a maintenance problem, because its parameter list is
likely to be changed in the future. For example, you can add a link
containing the book's cover as a fifth parameter. Consequently, every
code line that calls this function needs to be fixed. A better
solution is to pack the entire parameter list in a single struct or
class and pass it by reference to the function:

struct Item
{
string title;
// ..rest of the parameters
};
void retrieve(Item & book);

Packing the parameters in a single struct has two advantages. First,
every modification in the parameter list is localized to the
definition of the struct exclusively. You don't need to make changes
to every line of code that calls this function. In addition, you gain
a slight performance boost, because passing a single reference to a
function is more efficient than passing several arguments.


----------------------------------------------

STRUCTURED EXCEPTION HANDLING VERSUS STANDARD EXCEPTION HANDLING

Many C compilers provide a feature called structured exception
handling (SEH), which is not defined by the ANSI standard. SEH can
trap asynchronous, platform-specific exceptions such as division by
zero, floating point overflow and underflow, and other hardware
exceptions. In contrast, the standard C++ exception handling (EH)
applies only to synchronous exceptions--that is, exceptions that are
generated by the program itself and that are explicitly created by a
throw statement.

The types of exceptions raised by SEH and EH, as well as their
underlying mechanisms and recovery methods, are incompatible.
Therefore, never confuse SEH with EH, or vice versa. In C, use SEH if
necessary. In C++, stick to EH exclusively.


----------------------------------------------

THE BOOST WEB SITE

The Boost Web site offers assorted free C++ libraries and classes,
including a random number generator, four smart pointer classes, file
system tools, timer classes, numeric data types, and more. You can
also download classes that are still being developed and tested. The
Boost site is at

http://www.boost.org


----------------------------------------------

THE DANGERS OF OBJECT SLICING

Passing by value a derived object to a function may cause a problem
known as "object slicing"--every additional data member declared in
the derived object is sliced from the copy that the function receives.
Note also that the dynamic binding mechanism is inoperative in sliced
objects:

class Date
{
private:
int year, month, day;
public:
virtual void Display() const; /* mm-dd-yy */

};
class DateTime: public Date
{
private:
int hrs, min, sec; /* additional members; might be sliced off */
public:
void Display() const; /* mm-dd-yy hh:mm:ss */
};

void f(Date b) /* by value */
{
b.Display(); /* no dynamic binding; calls Date::Display() */
}
int main()
{
DateTime dt;
f(dt); /* sliced */
}

Object slicing may result in undefined behavior. Therefore, pass them
by reference whenever possible.


----------------------------------------------

THE USEFULNESS OF PTRDIFF_T

C and C++ define a special typedef for pointer arithmetic, ptrdiff_t,
which is a platform-specific signed integral type. You can use a
variable of type ptrdiff_t to store the result of subtracting and
adding pointers. For example:

#include < stdlib.h >
int main()
{
int buff[4];
ptrdiff_t diff = ( & buff[3] ) - buff; /*diff = 3*/
diff = buff -( & buff[3] ); /*diff = -3*/
}

There are two advantages in using ptrdiff_t instead of int. First, the
name ptrdiff_t is self-documenting and helps the reader understand
that the variable is used in pointer arithmetic. Second, ptrdiff_t is
portable: Its underlying type may vary across platforms, but you don't
need to make changes in the source code when porting your software to
other platforms.


----------------------------------------------

THE WELL-FORMEDNESS OF ZERO SIZED ARRAYS

In standard C++, declaring arrays with zero elements is ill-formed:

int n[0]; // compilation error

However, certain compilers do support arrays of zero size as
nonstandard extensions. In contrast, dynamic allocation of zero-sized
arrays is valid C++:

int n = new int[0];

The standard requires that, in this case, new allocate an array with
no elements. The pointer returned by new is non-null, and it is
distinct from a pointer to any other object. Similarly, deleting such
a pointer is a legal operation.

While zero-sized dynamic arrays may seem like another C++ trivia that
no one will ever need, this feature is chiefly important when
implementing custom memory allocators. A custom allocation function
can take any non-negative (i.e., unsigned) argument without worrying
whether it's zero:

void * allocate_mem(unsigned int size)
{
/* no need to check whether size equals zero*/
return new char[size];
}


----------------------------------------------

USE EXPLICIT ACCESS SPECIFIERS IN A CLASS DECLARATION

In many contexts, C++ provides default access specifiers when you
don't spell them out explicitly:

struct Employee{ /*..*/};
class Manager : Employee /* implicit public inheritance */
{
int rank; /* implicitly private */
bool StockHolder; /* ditto */
};

Relying on the default access specifiers is not recommended, though.
Always spell out the access specifier explicitly:

class Manager : public Employee
{
private:
int rank;
bool StockHolder;
};

class BoardMemebr : private Manager
{/*..*/};

By using explicit access specifiers, you ensure that the code is more
readable and you avoid future maintenance problems. Imagine that the
creator of struct Employee decides one day to change it into a class.
Consequently, Employee will become a private base class of Manager.


----------------------------------------------

USES OF THE OFFSETOF MACRO

The standard macro offsetof (defined in < stdlib.h > ) calculates the
offset of a struct member. The result is the number of bytes between
the beginning of the struct and that particular member. The following
example uses this macro to calculate the offset of two data members in
the struct S:

#include < stdlib.h >
struct S
{
int a;
int b;
void * p;
};
int main()
{
int n = offsetof(S, a); /*n = 0 */
n = offsetof(S, p); /* n = 8 */
}

Note that offsetof works with POD (Plain Old Data) structs and unions.
The result of applying this macro to a field that is a static data
member of a class is undefined. Likewise, applying offsetof to a class
that has virtual functions, base classes, or template members is
undefined, as well.


----------------------------------------------

USING FUNDAMENTAL TYPES AS A TEMPLATE PARAMETER

A template can take ordinary types such as int and char as its
parameters:

template < class T, int n > class Array
{/*..*/};

However, the template argument in this case must be a constant or a
constant expression. For example:

const int cn = 5;
int num = 10;
Array < char, 5 > ac; /*OK, 5 is a const*/
Array < float, cn > af; /* OK, cn is const*/
Array < unsigned char, sizeof(float) > au; /*OK, constant
expression*/
Array < bool, num > ab; /*error, num is not a constant */


----------------------------------------------

VISIT SILICON GRAPHIC'S STL PAGES

Silicon Graphics hosts a Web site dedicated to Standard Template
Library (STL). This site documents all of the classes, functions, and
concepts in the SGI implementation of STL. Each page describes a
single component and includes links to related components. The
documentation assumes a general familiarity with C++, and particularly
with C++ templates. The site also has a comprehensive introduction to
STL that can be useful for programmers who are familiar with C++ but
have never used STL. You can find the SGI STL pages at

http://www.sgi.com/Technology/STL/


----------------------------------------------

VISIT THE C/C++ USERS GROUP WEB SITE

The C/C++ Users Group (CUG) is an independent organization that offers
hundreds of classes, functions, and code libraries that you can
download from the site. CUG focuses primarily on cross-platform
compatibility with UNIX, Linux, Windows, DOS, and others. The code
packages include algorithms, mathematical libraries, logging and
reporting utilities, interpreters, compilers, file managers,
networking tools, and more. To find the CUG Web site, go to

http://www.hal9k.com/cug/


----------------------------------------------

WHY YOU SHOULDN'T STORE AUTO_PTR OBJECTS IN STL CONTAINERS

You must have heard that you shouldn't use auto_ptr objects as
elements of STL containers. Here's why.

The C++ Standard says that an STL element must be "copy-constructible"
and "assignable." These fancy words basically mean that for a given
class, assigning and copying one object to another are well-behaved
operations. In particular, the state of the original object shouldn't
change when it is copied to the target object.

This is not the case with auto_ptr, though: Copying or assigning one
auto_ptr to another makes changes to the original, in addition to the
expected changes in the copy. To be more specific, the original object
transfers ownership of the pointer to the target, and the pointer in
the original object becomes null. Imagine what would happen if you did
something like this:

vector < auto_ptr < Foo > > vf; /*a vector of auto_ptr's*/
// ..fill vf
int g()
{
auto_ptr < Foo > temp = vf[0]; /*vf[0] becomes null*/
}

When temp is initialized, the member vf[0] is changed: Its pointer
becomes null. Any attempt to use that element will cause a runtime
crash. This situation is likely to occur whenever you copy an element
from the container. Remember that even if your code doesn't perform
any explicit copy or assignment operations, some algorithms (swap(),
random_shuffle(), sort(), and many others) create a temporary copy of
one or more container elements. Furthermore, certain member functions
of the container create a temporary copy of one or more elements,
thereby nullifying them. Any subsequent attempt to the container
elements is therefore undefined.

Several Visual C++ users report that they have never encountered any
problems with using auto_ptr in STL containers. This is because the
auto_ptr implementation of Visual C++ (all versions thereof) is
outdated and relies on an obsolete specification. When the vendor
decides to catch up with the current ANSI/ISO C++ Standard and change
its Standard Library accordingly, code that uses auto_ptr in STL
containers will manifest serious malfunctions.

So, don't use auto_ptr in STL containers. Either use bare pointers or
use other smart pointer classes instead of auto_ptr. You can find such
classes at

http://www.Boost.org


----------------------------------------------

A C++ DECOMPILER--MYTH AND REALITY

A frequently asked question in C++ newsgroups is whether a C++ decompiler exists. It doesn't, and probably it never will. Why is this?

First, remember that there is no 1:1 relation between a piece of assembly code and a matching C++ statement. For example, for-loops, switch statements, and while blocks are all transformed into the same assembly directives. Thus, an infinite number of C++ programs can produce the exact assembly code.

The same is true for other language constructs that are translated into a common assembly construct: pointers, references, and arrays are all transformed into pointers. Furthermore, each compiler produces a different executable for a given source file. Even if you know the exact compiler brand and version that were used for compiling the program, how can you tell which compile-time options were used? And how can a decompiler reconstitute macros, constants, and typedefs, which are substituted with their underlying definitions before the compiler translates the source code into machine code?

Finally, debugging information is usually removed from the executable. Therefore, a hypothetical decompiler wouldn't be able to reconstitute the original names of variables, classes, arrays, pointers, functions, and constants. Thus, developing a utility that decompiles an executable file into its original C++ source code (or anything that comes near) is unrealistic.

--------------------------------------------------------------------------------

A POSSIBLE DEPRECATION OF VECTOR < BOOL >

In the early days of STL, C++ creators decided to include a specialized form of the vector container class called vector < bool >. This decision was plausible and seemed useful at that time. However, the experience gained in recent years shows that vector < bool > is problematic in many respects. It also contradicts some of the basic requirements with which STL containers must comply. For example, vector < bool > elements are bits. Yet a bit is not an addressable type--you can't define a pointer or a reference to a bit, nor can you allocate single bits using operator new. For this reason, algorithms and iterators can't cope with this type of vector. Worse yet, on most implementations, vector < bool > is significantly slower than other types of vector.

The C++ standardization committee is aware of these difficulties. Chances are good that future versions of the Standard will deprecate vector < bool >. In the meantime, it's advisable to avoid using it. If you need a vector of Boolean values, you can use vector of char instead.

--------------------------------------------------------------------------------

A PROBLEM WITH STATIC OBJECTS' INITIALIZATION

Examine this class and its global instance:

// file Foo.h
struct Foo
{
Foo() { RegisterSelf(); }
private:
void RegisterSelf() {/**/}
};

// file Foo.cpp
#include "Foo.h"
Foo reg; /*global*/

// file main.cpp
#include "Foo.h"
int main()
{
}

Surprisingly enough, the linker might "optimize away" the global object reg, even if you disable all compiler and linker optimizations. What's going on here? According to the C++ standard, the construction of an object defined in a namespace scope (a global object is considered as such) can be "deferred" indefinitely if no other function or object defined in the same translation unit refers to that object and if the object's constructor has no side effects. In other words, the linker may entirely remove reg's constructor call from the program because no other object or function in Foo.cpp refers to reg and because reg's constructor has no side effects.

To force the reg's construction, you must refer to it somewhere in Foo.cpp. Alternatively, you can add an operation with a side effect to reg's constructor, such as calling printf() from it.

--------------------------------------------------------------------------------

ASSIGNING A CHAR POINTER TO A STRING OBJECT

You can assign a pointer to char to a string object:


void f(const char *p)
{
std::string s = p; /* initialize s with a char pointer */
}
Note, however, that using a NULL pointer in this context yields undefined behavior, because string's member function doesn't check for a NULL pointer. If necessary, check the pointer to ensure that it's not NULL before assigning it to a string object. For example:


if (p)
{
std::string s = p; /* safe */
}

--------------------------------------------------------------------------------

ASSIGNING A ZERO VALUE TO ALL MEMBERS OF A STRUCT

Sometimes you need to clear all the members of a struct after it was used. For large structs, you usually use memset(). However, for small structs that occupy 2, 4, or 8 bytes, calling memset() is rather expensive in terms of performance:


struct Date
{
char day;
char month;
short year;
};

Date d;
d.day = 13; d.month = 4; d.year = 2000;
memset(& d, 0, sizeof (d)); /* inefficient */
You can avoid the overhead of calling memset() by using the following technique instead:


/* convert a pointer to d to a pointer to int */
int * pfake = reinterpret_cast < int* > (& d);
/* reset all members to zero through fake ptr */
*pfake = 0;
The first statement creates a pointer to int (assuming 32-bit int size) that points to a Date object, d. Cheating is necessary to convince the compiler to accept this conversion. Therefore, we use reinterpret_cast. The second line dereferences that fake int pointer and writes 0 to the memory block it points to. This ensures that all the bits in d are set to zero.

This two-step operation can be achieved with a single statement:

*( reinterpret_cast < int* > ( & d) ) = 0;

--------------------------------------------------------------------------------

AVOID EMPTY PARENTHESES IN OBJECT INSTANTIATION

Although the following statements are semantically equivalent:


string * p = new string(); /* empty parentheses */
string * p = new string; /* no parentheses */
the second one is preferable for two reasons. First, the parentheses are redundant anyway. More important, programmers who use the first form are often inclined to use empty parentheses when creating local automatic objects, as in:

string s(); /* probably an error */

The programmer mistakenly assumed that this statement creates a local string object, but it doesn't--it's interpreted as a declaration of a function named s, which returns a string object by value and takes no arguments. This is very different from the following:

string s;

This statement, on the other hand, creates an object named s of type string, which is what the programmer originally intended. As you can see, the bad habit of placing a redundant pair of empty parentheses when using new can be dangerous. Avoid it.

--------------------------------------------------------------------------------

BOOL CONVERSIONS

A value of type bool can be converted implicitly to a value of type int, with false becoming zero and true becoming one. For example:

bool b = false;
int n = b; /*n equals 0*/
b = true;
double f = b; /*f equals 1.0*/

Likewise, an arithmetic type, enumeration, pointer, or pointer to member can be converted to a value of type bool in the following manner: a zero value, null pointer, or null pointer to member is converted to false; any other value (including a negative value) is converted to true. For example:

void (A::*pmf)()= & A::f; /*pointer to a member*/
int n=-1;
bool b=pmf; /*b == true because pmf is not null*/
b = n; /*b == true*/
b = NULL; /* b == false*/
b = 9.99f; /* b == true */

--------------------------------------------------------------------------------

BORLAND'S C++ 5.5 IS NOW GIVEN AWAY--FREE

Inrpise (formerly Borland) is now distributing its C++ command-line compiler, Borland C++ 5.5, for free. You can download the compiler here (registration is required):

http://community.borland.com/

--------------------------------------------------------------------------------

CALLING A FUNCTION FROM AN OBJECT'S DESTRUCTOR

It's perfectly legal to call a member function, virtual or nonvirtual, from an object's destructor. However, you should avoid calling a member function that accesses data members that have already been destroyed. For example:


C::~C()
{
delete p;
this->serialize(p); /* undefined behavior */
}

--------------------------------------------------------------------------------

CHECKING A DATA ENTRY

Applications that accept users' input usually need to validate the data. For example, they have to detect whether the user entered alphabetic data instead of a number.

The easiest way to do that is to read a string rather than a numeric data type. After the user has entered the data, you can examine that string and check its validity.

--------------------------------------------------------------------------------

COMPILER'S WARNING MESSAGES ABOUT IDENTIFIER'S LENGTH

Unlike C, C++ does not impose any restrictions on the length of identifiers used in a program. Implementations set their own limits, but these are so high that names of variables, classes, functions, and constants can have thousands of characters. Although you wouldn't normally use more than 15 character names because of name-mangling, the compiler may extend an identifier's name with additional characters. For templates, the mangled names can be very long. In Visual C++, a typical mangled name of the cii iterator

std::vector < int >::const_iterator cii;

can exceed 255 characters. The mangled name of csi

std::vector< std::string >::const_iterator csi;

is even larger. Some debuggers can't cope with such long names and issue warning messages. You can safely ignore these messages because they are related only to the debugger's inability to display extremely large symbols. The compiler and the linker have no problem coping with these large names.

--------------------------------------------------------------------------------

CONTROLLING THE LIFETIME OF A LOCAL OBJECT

Sometimes, you need to force an object to destroy itself because its destructor has certain side effects, such as releasing a lock or closing a file. However, with a local automatic object, the destructor is invoked only when the current block exits. Before you suggest invoking the destructor explicitly, remember that the destructor will be called for the second time when the block exits with undefined behavior. Here is an example:

void func(Modem & modem)
{
Mutex mtx(modem); /*lock modem exclusively*/
Dial("1234567");
mtx.~Mutex(); /*explicit dtor call; bad idea!*/
use_modem();
}/*Mutex dtor called here once more*/

At this point, you would probably suggest allocating the object dynamically and deleting it when it's done. However, there is a simpler solution--wrap the object in braces:

void func(Modem& modem)
{
{/* an extra pair of braces */
Mutex mtx(modem);
Dial("1234567");
}/*mtx destroyed here*/
/*modem not locked anymore*/
use_modem();
}

--------------------------------------------------------------------------------

CONVERSION OF MEMBER-FUNCTION TO POINTER-TO-MEMBER-FUNCTION

Does you compiler accept this code?


class A
{
public:
void f(int);
};
void (A::*pm)(int) = A::f; /* #1 no ampersand before A::f */
A standard-compliant compiler should flag line #1 as an error. Let's examine it in further detail to see why. The expression A::f is called a qualified-id. A qualified-id denotes a member of a class. According to the C++ standard, there is no implicit conversion from a qualified-id denoting a member function to the type "pointer to member function". In other words, an ampersand must appear before the qualified-id if you want to take the address of the member function. For example:

void (A::*pm)(int) = & A::f; /* now OK */

--------------------------------------------------------------------------------

CONVERTING A DATA TYPE TO A STREAM OF BYTES

Sometimes you need to convert a certain data type to a binary stream of bytes. This is particularly useful in dump utilities and debuggers. To do that, create a union that has two members: the type in question and an array of characters with the same size. For example, to examine the binary representation of a pointer to a member function, the following program assigns the address of the member function f() to the union member p and then it examines the corresponding character array bin_ptr:

class A
{
public:
void f();
};

typedef void (A::*pmf)(); /*pointer to member*/
union U
{
pmf p;
unsigned char bin_ptr[sizeof(pmf)];
};
int main()
{
U u={0}; /*initialize*/
u.p= & A::f; /*assign member function's address*/
for (int j = 0; j < sizeof (U); j++)
printf("%d\n", u.bin_ptr[j]);/*dump p*/



--------------------------------------------------------------------------------

COPYING AND ASSIGNMENT OF CONTAINER OBJECTS

STL containers define an appropriate copy constructor and overload the assignment operator, thereby enabling you to copy construct and assign container objects easily:


int main()
{
vector < int > first;
/*.. fill first */
vector < int > second (first); /* copy construct */
second.push_back(4);
first = second; /* copy all elements of second to first */
}

--------------------------------------------------------------------------------

CORRECTLY DECLARING GLOBAL OBJECTS

Although the use of global variables is not recommended in general, sometimes it's unavoidable. When using a global variable or object, never instantiate it in a header file because the header is usually #included in several separate source files. Consequently, the linker will complain on multiple definitions of the same object. Instead, instantiate a global in a single .cpp file. This way, you ensure that it's defined only once, regardless of the number of source files used in the project. All other source and header files in the project that refer to that global object need to declare it extern. Here is an example:


// File a.h
/*********/

/*declaration only; x is defined in another file*/
extern int x;

struct Counter
{
Counter() {++x;}
~Counter() {--x;}
};

// File b.cpp
/*********/

int x; /* definition of a global variable */

// File main.cpp
/*********/

#include "a.h"
int main()
{
Counter count;
int n = x;
}
The two source files, b.cpp and main.cpp, are compiled separately. At link time, the linker resolves all references to x to the variable defined in b.cpp.

--------------------------------------------------------------------------------

CREATING AN ARRAY OF OBJECTS WITH NO DEFAULT CONSTRUCTOR

The following class doesn't have a default constructor:


class A
{
public:
A(int x, int y);
};
Therefore, this array declaration will not compile:

A arr[10]; /* error; no default constructor for class A*/

However, you can still create arrays of class A using an explicit initialization list:

A arr[3] = { A(0,0), A(0,0), A(0,0) }; // ok

Note that in the declaration of the array arr, every element must be explicitly initialized. This is tedious, especially if you create a large array. Furthermore, you cannot create an array of A dynamically:

A * pa = new A[2]; // error

Therefore, when you define a constructor for a class, remember to define a default constructor as well.

--------------------------------------------------------------------------------

DECLARING A TYPEDEF

As you may know, typedef names can hide intricate syntactic constructs such as pointers to functions or complex template declarations. However, many novices simply don't know how to declare or interpret a typedef declaration.

To write a typedef declaration, simply define a variable of the desired type. Suppose you want to create a typedef name that represents "pointer to int". First, define a pointer to int:

int * pi;

Then, precede the keyword "typedef" to the previous declaration:

typedef int * pi;

Now the name pi serves as a synonym for "pointer to int". You can use pi in any context that requires a pointer to int:

pi ptr = new int;

Similarly, to create a typedef name that is synonymous with "pointer to function that returns int and takes int", you first declare such a pointer:

int (*pfi)(int);

In the definition above, pfi is a pointer to a function that returns int and takes int. Now, add the keyword "typedef" before the definition:

typedef int (*pfi)(int);

And pfi becomes a typedef name for "pointer to function that returns int and takes int". For example:


int func(int);
void callback( pfi some_func)
{
int result = some_func(5);
}

--------------------------------------------------------------------------------

DECLARING POINTERS TO DATA MEMBERS

Although the syntax of pointers to members may seem a bit confusing at first, it's consistent and resembles the form of ordinary pointers, with the addition of the class name followed by the operator :: before the asterisk. For example, if an ordinary pointer to int looks like this:

int * pi;

you define a pointer to an int member of class A like this:

int A::*pmi; /* pmi is a pointer to an int member of class A */

You initialize a pointer to a member like this:


class A
{
public:
int num;
int x;
};
int A::*pmi = & A::num; /* 1 */
The statement numbered 1 declares a pointer to an int member of class A and initializes it with the address of the member num. Using pmi and the built-in operator .*, you can examine and modify the value of num in any object of class A:


A a1, a2;
int n = a1.*pmi; /* copy a1.num to n */
a1.*pmi = 5; /* assign the value 5 to a1.num */
a2.*pmi = 6; /* assign the value 6 to a2.num */
If you have a pointer to A, you need to use the built-in operator ->* instead:


A * pa = new A;
int n = pa->*pmi;
pa->*pmi = 5;

--------------------------------------------------------------------------------

DECLARING POINTERS TO MEMBER FUNCTIONS

Pointers to member functions consist of the member function's return type, the class name followed by ::, the pointer's name, and the function's parameter list. For example, a pointer to a member function of class A that returns an int and takes no arguments is defined like this (note that both pairs of parentheses are mandatory):


class A
{
public:
int func ();
};

int (A::*pmf) ();
In other words, pmf is a pointer to a member function of class A that returns int and takes no arguments. In fact, a pointer to a member function looks just like an ordinary pointer to a function, except that it also contains the class's name immediately followed by the :: operator. Using operator .*, you invoke the member function to which pmf points like this:


pmf = & A::func;
A a;
(a.*pmf)(); /* invoke a.func() */
If you have a pointer to an object, you use the operator ->* instead:


A *pa = & a;
(pa->*pmf)(); /* calls pa->func() */
Pointers to member functions respect polymorphism. Thus, if you call a virtual member function through such a pointer, the call will be resolved dynamically.

Note that you can't take the address of a class's constructor(s) and destructor.

--------------------------------------------------------------------------------

DEFAULT INITIALIZATION

A POD (plain old data) type is a union, struct, or class with no user-declared constructors, no private or protected nonstatic data members, no base classes, and no virtual functions. To default-initialize an object of type T means one of the following:

* If T is a non-POD class type, the default constructor for T is called.
* If T is an array type, each element is default-initialized. Otherwise, the storage for the object is zero-initialized.

An object whose initializer is an empty set of parentheses is default-initialized. For example:


struct Date /* POD type */
{
int day;
int month;
int year;
};
Date d = Date(); /* default initialize d */
Because d is of type POD, the explicit default initialization of d initializes all its members to 0. Since the default initialization rule was added recently to C++, many compilers don't implement it yet. Therefore, you should use an explicit initialization list to ensure that d is zero-initialized:

Date d = {0}; /* zero initialization */

--------------------------------------------------------------------------------

DELETING CONST POINTERS

In earlier stages of C++, deleting a const object, even if that object was allocated dynamically, was impossible. However, the C++ Standard was recently changed and now you can use operator delete to destroy such objects. For example:


class Foo{};
int main()
{
const char * p = new char[12]; /* ptr to const char */
const Foo * const f = new Foo; /* const ptr to const Foo */
delete p; /* fine */
delete f; /* fine */
}
Notice that some compilers may not support this feature yet.

--------------------------------------------------------------------------------

DENNIS RITCHIE'S HOME PAGE

Dennis Ritchie was the creator of C and a co-author of the legendary book "The C Programming Language." His Web site is probably the best virtual museum of the C programming language. The site includes interesting historical documents, pictures, and memos from the early days the C programming language. Amazingly enough, the original source files of the primeval C compiler (from 1972 or so) are available online! You can find it at

http://cm.bell-labs.com/cm/cs/who/dmr/

--------------------------------------------------------------------------------

DETECTING AN UNCAUGHT EXCEPTION

An exception is considered caught when its handler has started or when the standard function unexpected() has been invoked, if no matching handler was found. To check whether an uncaught exception is being processed, use the standard function uncaught_exception() (declared in the standard header < exception > ). For example:


#include < exception >
using std::uncaught_exception
int main()
{
bool b = uncaught_exception();
}

--------------------------------------------------------------------------------

DIFFERENCES BETWEEN FLOAT AND DOUBLE

In most cases, double is the default floating-point data type you should use. Variables of type double offer higher precision and minimize truncation and deviation to a minimum. This is why most of the functions and algorithms in the Standard Library take and return double. In general, float should be used only to save memory or when the use of double imposes additional processing overhead on the CPU.

--------------------------------------------------------------------------------

DIFFERENCES BETWEEN VECTOR'S RESERVE() AND RESIZE() MEMBER FUNCTIONS

Although the reserve() and resize() member functions of std::vector perform similar operations, they differ in two respects: Unlike resize(), which allocates memory in the requested size and default-initializes it, reserve() allocates memory in the requested without initializing it. In addition, reserve() does not change the size (that is, the number of elements) of a vector; it changes only the vector's capacity. Note that both these functions take the number of elements, not bytes, as their argument. For example:


vector < int > vi;
vi.resize(1000); /* make room
for at least 1000 int's and initialize them to 0
*/
vi.reserve(2000); /* make room
for at least 2000 int's without initialization
*/

--------------------------------------------------------------------------------

DO NOT RETURN CONTAINER OBJECTS BY VALUE

The overhead of returning an ordinary object by value may be acceptable in most cases. However, returning a container object by value is much more expensive in terms of performance. For example:


vector < int > f(int n) /* returns a vector by value */
{
vector < int > vi(n);
return vi; /* extremely inefficient */
}
Remember that when you return an object by value from a function, the object copy-constructs itself on the stack. When returning a container, this process consists of copy-constructing its elements one by one. Therefore, you should always pass and return containers by reference.

--------------------------------------------------------------------------------

DON'T CHANGE THE ACCESS OF A VIRTUAL MEMBER FUNCTION IN A DERIVED

CLASS

 

Although it's perfectly legal to change the access specifier of a

virtual member function in a derived class, as in

 

class Base

{

public:

virtual void Say();

};

class Derived : public Base

{

private: /* access specifier changed */

void Say();

};

 

you should never do that. When you use a pointer or a reference to a

base class, the object bound to that pointer or reference will behave

unpredictably regarding the access specification of its members:

 

Derived d;

Base *p = & d;

p->Say(); /* surprise */

 

Because the binding of a virtual member function is postponed to

runtime, the compiler cannot detect that a nonpublic member function

is called; although p points to an object of type Derived, in which

Say() is a private member function, the compiler assumes that p points

to an object of type Base, of which Say() is a public member.

Therefore, the code compiles fine when, seemingly, it shouldn't.

 

 

 

----------------------------------------------

 

DON'T CONFUSE DELETE WITH DELETE[]

There's a common myth among programmers that it's okay to use delete instead of delete[] to release arrays of built-in types. For example,


int *p = new int[10];
delete p; /* bad; should be: delete[] p */
This is totally wrong. The C++ standard specifically says that using delete to release dynamically allocated arrays of any type yields undefined behavior. The fact that, on some platforms, applications that use delete instead of delete[] don't crash can be attributed to sheer luck: Visual C++, for example, implements both delete[] and delete for built-in types by calling free(). However, there is no guarantee that future releases of Visual C++ will adhere to this convention. Furthermore, there's no guarantee that this code will work on other compilers. So, using delete instead of delete[], and vice versa, is precarious and should be avoided.

--------------------------------------------------------------------------------

ELIMINATING TEMPORARY OBJECTS

C++ creates temporary objects "behind your back" in several contexts. The overhead of a temporary can be significant because both its constructor and destructor are invoked. You can prevent the creation of a temporary object in most cases, though. In the following example, a temporary is created:

Complex x, y, z;
x = y+z; /* temporary created */

The expression y+z; results in a temporary object of type Complex that stores the result of the addition. The temporary is then assigned to x and destroyed subsequently. The generation of the temporary object can be avoided in two ways:

Complex y,z;
Complex x = y+z; /* initialization instead of assignment */

In this example, the result of adding x and z is constructed directly into the object x, thereby eliminating the intermediary temporary. Alternatively, you can use += instead of + to get the same effect:

/* instead of x = y+z; */
x =y;
x+=z;

Although the form that uses += is less elegant, it costs only two member function calls: assignment operator and operator +=. In contrast, the use of + results in three member function calls: a constructor call for the temporary, a copy constructor for x, and a destructor call for the temporary.

--------------------------------------------------------------------------------

FALSE MEMORY LEAKS

Global objects and objects declared in a namespace scope are constructed before main() starts. The implementation usually runs special startup code that constructs such objects (including cin and cout) and performs additional initializations before main() starts. Because the memory that these objects allocate is not released before main() exits, some utilities and debugging tools mistakenly report that these objects leak memory. There's no need to worry, though. The memory allocated by cin, cout, and similar objects doesn't really leak; it's released when these objects are destructed, an operation that takes place after main() exits.

--------------------------------------------------------------------------------

FUNCTION PARAMETERS CANNOT BE USED AS DEFAULT ARGUMENTS

The order of evaluation of function arguments is unspecified. Therefore, parameters of a function may not be used in default argument expressions. The following declaration is ill formed because it uses a parameter as a default argument:

int f(int n, int m = n); /* error */

--------------------------------------------------------------------------------

HANDLING STRING EXCEPTIONS

Several member functions of std::string might throw exceptions. For example, if you use a very large number to specify the initial storage size, string's constructor might throw an exception:

string s(string::npos, 'c'); /* throws std::length_error */

In practice, values that are much smaller than the constant string::npos might also cause a std::length_error exception, depending on the program's available heap size. For this reason, it's advisable to check the value you pass to the constructor in advance. Alternatively, you can use try and catch statements to handle the exception.

--------------------------------------------------------------------------------

HEAP AND FREE-STORE

The terms "heap" and "free-store" are used interchangeably when referring to dynamically allocated objects. Are there any differences between the two?

Technically, free-store is an abstract term referring to unused memory that is available for dynamic allocations. Heap is the data model used by virtually all C++ compilers to implement the free-store. In practice, however, the distinction between heap and free-store is roughly equivalent to the differences between the memory models of C and C++, respectively. In C, you allocate memory dynamically using malloc(), and release the allocated memory using free(). The allocated memory is not initialized. Dynamic allocations in C are said to take place on the heap. In C++, you perform dynamic allocations with new and release the allocated memory with delete. Operator new automatically initializes the allocated memory (by calling the object's constructor), and delete automatically destroys the object before releasing its memory. In C++, dynamic allocations are said to take place on the free-store.

Note that the heap and free-store may reside on distinct physical memory regions and they might be controlled by different underlying memory managers.

--------------------------------------------------------------------------------

INITIALIZING A BIT STRUCT

To initialize a struct that contains bit fields, simply use the ={0} partial initialization list:


struct DATA_HEADER
{
unsigned Sync:11;
unsigned Version:2;
unsigned Layer:2;
// ..more members
};
/* create an instance and initialize it */
DATA_HEADER header = {0}; /*set all members to zero*/
When you use a ={0} partial initialization list, both C and C++ guarantee that all the bit fields in the struct are zero initialized.

--------------------------------------------------------------------------------

INITIALIZING A VECTOR WITH THE CONTENTS OF AN ARRAY

Built-in arrays meet the criteria of an STL sequence. Therefore, you can directly initialize a vector with a built-in array:


int main()
{
int arr[3] = {1, 2, 3};
vector < int > vi ( arr,
arr+3 );
/* points one element past array's end
*/

}
To copy the entire array arr into vi, the first argument in the declaration of vi must be the address of the first array element, whereas the second argument must point to the first element PAST the array's end.

--------------------------------------------------------------------------------

INTERNET C++ VIRTUAL MACHINE

Implementing a Java-style virtual machine for C++ is a trivial task. The problem is that such a virtual machine would suffer from the well-known ailments of Java: bloated and inherently slow code. At present, at least three independent projects are attempting to define a uniform binary interface for C++ without compromising performance or code size. One of these projects will enable you to link code that was compiled by different compilers into a single executable file. A more interesting project is targeted at the Internet. The Internet C++ Virtual Machine (ICVM) project defines a universal 32-bit virtual machine for C++ that will enable you to run C++ code on every target machine, regardless of the underlying OS and hardware architecture. ICVM promises both high performance and portability. You can read more about ICVM here:

http://sr9.xoom.com/icvmcpp/

--------------------------------------------------------------------------------

INTRODUCING POINTERS TO MEMBERS

A class can have two general categories of members: function members and data members. Likewise, there are two categories of pointers to members: pointers to member functions, and pointers to data members. The latter are less common, because in general, classes do not have public data members. However, when using legacy C code that contains structs or classes that happen to have public data members, pointers to data members are useful.

Pointers to members are one of the most intricate syntactic constructs in C++, and yet, they are a very powerful feature, too. They enable you to invoke a member function of an object without having to know the name of that function. This is very handy implementing callbacks. Similarly, you can use a pointer to a data member to examine and alter the value of a data member without knowing its name. In future tips, I will discuss pointers to members in further detail and show you how to define and use them.

--------------------------------------------------------------------------------

ITERATORS AND REAL POINTERS

Suppose you define the following list object and you need the address of one of its elements:


std::list < int > li;
std::list < int >::iterator iter = li.begin();
In many contexts, iter functions like a pointer. However, when you need a real pointer to a container's element, you can't use an iterator:


int func(int * p);
int main()
{
func(iter); /* error, iter is not a pointer to int */
}
The problem is that in general, iterators aren't pointers; they are usually implemented as objects. To get a pointer to the element an iterator refers to, you need to "dereference" that iterator and take the address of the result. For example:


int main()
{
func( & *iter); /* OK */
}

--------------------------------------------------------------------------------

LOCAL OBJECTS' DESTRUCTION ORDER

On exit from a scope, destructors are called for all local automatic objects that are declared in that scope, in the reverse order of their declaration. For example:


void f()
{
CmyClass obj1;
CmyClass obj2;
}
obj1 is constructed before obj2. On exit from f(), the objects are destroyed in the reverse order of their declaration, so obj2 will be destroyed before obj1.

--------------------------------------------------------------------------------

MEMORY ALLOCATIONS IN SINGLE-THREADED APPLICATIONS

On most platforms, the operators new and delete are implemented in a thread-safe manner so that they can be used in multithreaded applications. The thread-safe implementation of these operators imposes an unnecessary runtime overhead in single-threaded applications. Therefore, in time-critical single-threaded applications, you may consider overloading new and delete for particular classes or using a custom memory pool.

--------------------------------------------------------------------------------

MINIMIZING CODE BLOAT ASSOCIATED WITH TEMPLATES

Incautious usage of templates can cause a problem known as code bloat: The compiler reduplicates the template's body for each distinct specialization (template instance), and as a result, the program's size increases dramatically. In the following example, three distinct copies of the template min() will be generated--one for each type used:


template < class T > T min(T f, T s)
{
return f < s? f: s;
}
int j = min(6,8); /* min < int > */
char k = min('c', 'd'); /* min< char > */
unsigned int w = min(10u, 9u); /* min< unsigned int > */
Sometimes you can avoid such unnecessary instantiations by casting the arguments into one common type:


char k = min( static_cast < int > ( 'c'),
static_cast < int > ('d') ); /* min<int> used */

This technique can be applied to pointers, too: First, cast them to void * and then cast back to their original type. Notice that for very small templates like min(), this technique isn't worth the bother. However, when using larger templates, you might want to consider its use.

--------------------------------------------------------------------------------

MORE ON OPERATOR OVERLOADING

Here are some pointers on operator overloading. First, you cannot invent a new operator during overloading:


void operator @ (int) ; /* illegal */
Second, neither the precedence nor the number of arguments that the operator takes can be changed. For example, an overloaded + takes exactly two arguments (remember that when you overload an operator as class member, the operator always has an extra hidden argument).


Finally, the following operators cannot be overloaded:
.
.*
::
?
sizeof
Similarly, static_cast, dynamic_cast, reinterpret_cast, and const_cast, as well as the # and ## preprocessor tokens, may not be overloaded.

--------------------------------------------------------------------------------

NAMESPACE ALIASES

 

Choosing a short namespace name might eventually lead to name clashes.

On the other hand, very long namespaces tend to be laborious. For this

purpose, you can use a namespace alias. A namespace alias is a synonym

for another namespace. For example:

 

namespace spring_project

{

class Date{ /*..*/ };

const int MAX_NUM = 512;

/* .. more stuff */

}

 

int main()

{

namespace sp = spring_project; /* sp is a namespace alias */

sp::Date d; /* equivalent to spring_project::Date but shorter */

}

 

 

 

----------------------------------------------

 

OPERATORS CANNOT BE OVERLOADED FOR USER-DEFINED TYPES

An overloaded operator must take at least one argument of a user-defined type (operators new and delete are the only exception). This rule ensures that users cannot alter the meaning of expressions that contain only built-in types. For example:


int i, j, k;
k = i + j; /* uses built-in = and + operators */
Note that C++ differs in this respect from other languages that support operator overloading, such as Ada. Recall that enum types are user-defined types; as such, you can define overloaded operators for them, too.

--------------------------------------------------------------------------------

PERFORMING CASE-INSENSITIVE COMPARISON OF STRINGS

The overloaded operator == performs a case-sensitive comparison of string objects. If you check the following strings for equality using ==, the result will be false:

string s1 = "Jellylorum";
string s2 = "JellyLorum";

For most applications, this is the expected behavior. However, sometimes it's necessary to perform case-insensitive comparisons. Unfortunately, there is no standard version of operator == that performs a case-insensitive comparison. There are several workarounds, most of which require familiarity with the underlying machinery of STL, the notion of char traits, function objects, binders, and so on. For many programmers who simply wish to perform case-insensitive comparisons, these solutions are too complicated. The simplest way to get around this is by overloading the standard C function strcimp(). The new overloaded version takes strings rather than char *:


#include < string.h > // for stricmp()
#include < string >
inline int stricmp (const std::string & s1,
const std::string & s2)
{
return stricmp (s1.c_str(), s2.c_str()); /* C's stricmp */
}
Note that this function, like its C counterpart, performs a lexicographical comparison. The result can be -1, 0, or 1. If you prefer a bool result, change its body to

return stricmp (s1.c_str(), s2.c_str()) == 0;

Now you can use the overloaded function:

bool b = stricmp(s1,s2);

--------------------------------------------------------------------------------

PLACE INCLUDE GUARDS IN YOUR HEADER FILES

Including a file more than once during the same compilation session is likely to cause errors due to multiple declarations. To avoid this, place an include guard in every header file. For example:


// file Chrono.h
#ifndef CHRONO_H /* include guard */
#define CHRONO_H true

/* declarations come here */
class Chrono { /*...*/ };
typedef std::vector < Chrono > chronvec;

#endif /* appears at the end of the file */
When the preprocessor reads the file Chrono.h for the first time, it defines the macro CHRONO_H and scans the file's contents. Any subsequent inclusion of this file has no effect, because the #ifndef directive in the file's beginning will evaluate to false; consequently, the file's contents will be ignored. Note that all of the Standard Library's headers have such include guards.

--------------------------------------------------------------------------------

PREFER STL CONTAINERS TO HOMEMADE ONES

Many programmers still use homemade (or worse yet--handmade) container classes such as list and vector instead of using the Standard Template Library (STL). There are significant advantages in preferring STL containers to any other container framework: STL containers are portable, their performance is usually superior, and they have already been debugged and tested for you.

--------------------------------------------------------------------------------

PREFIXES OF HEXADECIMAL AND OCTAL LITERALS

Hexadecimal and octal literals are indicated by special prefixes: 0x and 0, respectively. For example:

int n =0x1000; // hex
n = 01000; // octal

--------------------------------------------------------------------------------

PREVENTING MEMORY LEAKS

A memory leak occurs when a program fails for some reason to release memory that it allocated on the heap. The program won't reclaim such leaked memory because both C and C++ assume that if you don't explicitly free dynamically allocated memory, you want it to remain available throughout the lifetime of the program. Some operating systems reclaim leaked memory after the process has terminated. On other operating systems (DOS, for example), leaked memory is lost until the system is rebooted. In either case, recurrent memory leaks cause an application to bog down or crash altogether.

The easiest way to avoid memory leaks is by not using dynamic allocations in the first place. If possible, use automatic and static memory instead. When dynamic memory allocation is unavoidable, remember to add a matching delete call for every object allocated by new.

--------------------------------------------------------------------------------

REFERENCE COUNTING ON STRING

C++ allows an implementation to use reference counting on std::string. However, this is not a requirement. A reference-counted implementation must have the same semantics as a nonreference-counted one. For example:


string str1("xyz");
string::iterator i = str1.begin();
string str2 = str1;
*i = 'w'; /* must modify only str1*/

--------------------------------------------------------------------------------

ROUNDING NUMBERS

The standard < math.h > header declares the function ceil() and floor() that round the fractional part of a floating point number. ceil() returns the smallest integer that is no less than its argument. For example, ceil(5.4) returns 6. By contrast, floor() returns the largest integer that isn't greater than its argument. For example, floor(5.4) returns 5.

--------------------------------------------------------------------------------

SAFE USE OF INLINE

Excessive use of inline functions might bloat the size of the executable and reduce execution speed. For this reason, many programmers avoid function inlining altogether because they can't predict how inlining will affect performance. However, very short functions (for example, a function that merely calls another function or a function that returns a data member of its object) are always good candidates for inlining. For example:


inline bool Foo::is_connection_alive()
{
return ::connection_alive(); /* call an API function */
}
inline int Bar::get_x()
{
return x; /* return a data member */
}
When you inline such functions, the size of the executable decreases slightly and execution speed is boosted. Note, however, that for larger functions, you usually need to profile the code with and without "inline" to assess the effects of inlining.

--------------------------------------------------------------------------------

SEARCHING FOR PATTERNS OF TEXT IN WORDPERFECT

WordPerfect's comprehensive Find feature allows you to search for text in a number of ways. In addition to searching for the standard exact matches, you can specify a broader search by using wild cards in your searches. A wild card represents an unknown character in a search string. WordPerfect has two wild-card options: one unknown character and many unknown characters. If you need to find all instances of the company name XYZ Company to confirm that you've referenced it consistently throughout your document (as Company rather than Co. or Corp. or Corporation), you could use a many-character wild card in the search string.

Press Ctrl-F to open the Find feature and type

XYZ Co

in the Find text box. Choose Match, Codes from the menu, select *(Many Char) from the codes list, and click Insert & Close. Your search string will be

XYZ Co[*(Many Char)]

To start the search, click Find Next. WordPerfect will find all instances of phrases starting with XYZ Co. A single unknown character search limits the variable character to one letter. For example, to find all instances of a three-letter word beginning with A and ending with C (AAC, ABC, ACC, and so on), you would type

A

in the Find box, choose Match, Codes from the menu, select ?(One Char) from the code list, click Insert & Close, and type

C

at the end of the search string, so it looks like this:

A[?(One Char)]C

--------------------------------------------------------------------------------

SOME DESIGN AND CODING RULE OF THUMBS

Writing code from scratch, without any prior design or plan, is bad programming practice. However, the opposite--over-engineering--can be just as harmful. Over-engineering is the use of costly, redundant, or "cute" features that aren't truly necessary or that have simpler and efficient alternatives. A good example of this is using exception handling as an alternative to while and for statements. Similarly, templatizing a class or functions that are never used for more than a single type is an expensive and redundant task. "Cute" features and puns such as macro trickery can also result in indecipherable code. However, the worst of all is probably a wheel's reinvention. Writing custom container classes and algorithms instead of using the ones that are readily available in C++ (in the form of STL) is expensive, bug prone, and totally uncalled for. One of the phases of design and code reviews should consist of catching and sifting such instances of over-engineering, wheel reinvention, and "poetry."

--------------------------------------------------------------------------------

STATIC INITIALIZATION AND DYNAMIC INITIALIZATION

C++ distinguishes between two initialization types for objects with static storage duration (global objects, local static objects, and objects declared in namespace scope have static storage). Static initialization consists of either zero-initialization or initialization with a constant expression; any other initialization is considered dynamic initialization. These two types roughly correspond to compile-time initialization and runtime initialization. For example:


int x = func();
int main()
{
}
The global variable x has static storage. Therefore, it's initialized to 0 at the static initialization phase (by default, objects with static storage are zero-initialized). The subsequent dynamic initialization phase initializes x with the value returned from the function func(). This operation can take place only at runtime.

In other words, objects with static storage may be initialized twice: once by static initialization, during which their memory storage is initialized to binary zeroes, and afterwards, when their constructors perform additional dynamic initialization.

--------------------------------------------------------------------------------

SWAP WITHOUT A TEMPORARY

The classic implementation of swap looks like this:

void swap (int & i, int & j)
{
int temp = i;
i = j;
j = temp;
}

Is it possible to swap i and j without using a third variable? Yes, it is:

swap(int & i, int & j)
{
i-=j;
j+=i; /*j gets the original value of i*/
i=(j-i); /*i gets the original value of j*/
}

However, this version isn't more efficient than the previous one (in fact, it's probably less efficient) because the compiler must generate a temporary variable to evaluate the expression i = (j - i).

Although it has no practical value, this exercise is not completely useless: it may show up in your next job interview, so it's worth remembering how to solve it.

--------------------------------------------------------------------------------

THE HIDDEN COST OF A VIRTUAL FUNCTION

You're probably aware of the overhead associated with calling a virtual member function. However, the performance penalty might even be higher than it first seems. Usually, compilers can't inline a virtual function call. Therefore, the performance cost consists of the inability to inline a function plus the additional overhead of dynamic binding. Many programmers aren't aware of the double overhead when they declare member functions virtual. As a rule, don't declare a member function virtual unless it really needs to be virtual.

--------------------------------------------------------------------------------

THE LINKAGE TYPE OF GLOBAL CONST OBJECTS

In C++ (but not in C), a const object declared in the global scope has internal linkage. This means that a const object that apparently looks like a global one is visible only in the scope of its source file; it isn't visible from other files unless you explicitly declare it extern:


/* File a.cpp */

const int x=0;

/* File b.cpp */
const int x = 2; /* a different x */

/* File main.cpp */
int main()
{
}
Both a.cpp and b.cpp define a const variable called x. However, the two definitions don't clash because they refer to distinct variables, each of which has a different value and is visible only from the scope of its source file. If you remove the const qualifier from the definition of both x's, recompile all the source files, and relink them, you will receive a linker error:

Public symbol x defined in both module a.obj and b.obj

This is because x has external linkage when it's not declared const. As a result, the two definitions of x clash.

There are two lessons from this example. First, if you wish to make a const object declared in the global scope globally accessible, you must declare it extern. Second, avoid declaring nonlocal const objects static, as in

static const int x = 0;

This is both redundant and deprecated.

--------------------------------------------------------------------------------

THE NAMED RETURN VALUE OPTIMIZATION

The named return value (NRV) is an automatic optimization that compiler applies to eliminate the construction and destruction of an object returned by value from a function. When is the NRV applied? When a temporary object is copied to another object using a copy constructor, and none of these objects is const or volatile, C++ allows the implementation to treat the two objects as one and not perform a copy at all. For example:


class A
{
public:
A();
~A();
A(const A & );
A operator=(const A & );
};

A f() /* returns A by value */
{
A a1;
return a1;
}

A a2 = f();
Theoretically, f() constructs a local object a1 and creates a temporary copy of a1 on the stack when the return statement is executed. Finally, a2 is copy-constructed using that temporary object. This is very inefficient, though. The NRV optimization eliminates the construction of the local object in f() as well as the creation of its temporary copy on the stack. Instead, the return value of f() is constructed directly into the object a2, thereby avoiding the construction and destruction of both the local object a1 and its temporary copy on the stack.

--------------------------------------------------------------------------------

THE PRESENT AND FUTURE OF LONG

On 32-bit platforms, int and long usually occupy the same size. Programmers use these types interchangeably. However, on 64-bit architectures, long is often a 64-bit integer, whereas int occupies 32 bits. In the next few years, many platforms and processors will switch to a 64-bit word size. To avoid future maintenance headaches and portability problems, use plain int or an equivalent 32-bit typedef when you need a 32-bit data type. If you really need a 64-bit sized integer, use either long long or a nonstandard type such as __int64. These types have explicit sizes, so they are more portable than plain long.

--------------------------------------------------------------------------------

THE RANDOM_SHUFFLE ALGORITHM

The STL random_shuffle() algorithm shuffles the elements of a sequence. This algorithm takes two arguments: an iterator that points to the first element of the sequence and an iterator that points one position past the last element of that sequence. The following program defines a vector of characters and fills it with four consecutive elements. Next, random_shuffle() is called to shuffle the elements in the vector:


#include < algorithm > /* for random_shuffle */
#include < vector >
using namespace std;
int main()
{
vector<char> vc;
vc.push_back('a'); // fill vector
vc.push_back('b');
vc.push_back('c');
vc.push_back('d');
random_shuffle(vc.begin(), vc.end()); /* shuffle elements */
}
You can use random_shuffle() with built-in arrays too:

int n[4] = {0,1,2,3};
random_shuffle(n, n+4);

--------------------------------------------------------------------------------

THE RELEVANCE OF THE SINGLE EXIT POINT IDIOM

Many programming schools recommend that functions have only one exit point. Although the "single exit point" idiom has some merits in procedural languages, it's virtually useless in C++. To see why, look at this function. Outwardly, it complies with the single exit idiom:

int func()
{
char * pbuff = new char [MAX]; /*#1*/
std::string str ("hello"); /*#2*/
return 0; /*only one exit?*/
}

Although it may not be immediately visible, func() has three exit points, one of which is an explicit return statement. The other two are implicit: In statement #1, if new fails, it throws a std::bad_alloc exception (throwing an exception is an exit point). Similarly, in statement #2, if string's constructor fails, it also throws an exception. Thus, the single exit point design idiom is irrelevant in nontrivial C++ code. Instead of following obsolete principles, it's best to focus on robust, efficient, and functional design.

--------------------------------------------------------------------------------

THE REPLACE() ALGORITHM

STL's replace() algorithm is defined in the standard header <algorithm> and has the following prototype:

void replace(ForwardIterator start,
ForwardIterator end,
const T & old_value,
const T & new_value);

The first and the second arguments mark the sequence's beginning and end, respectively. replace() changes every occurrence of old_value in the sequence to new_value.

The following example uses replace() to replace every occurrence of "NT" with "Win2000" in a vector of strings:

#include < algorithm >
#include < string >
#include < vector >
using namespace std;
main()
{
vector < string > vs;
// fill vector
vs.push_back("Unix");
vs.push_back("VMS");
vs.push_back("NT");
// replace every occurrence of "NT" by "Win2000"
replace (vs.begin(),vs.end(),"NT","Win2000");
}

You can apply replace() to built-in arrays, too:

int n[]={1,0,2,1};
replace(n, n+4,1,5);/*replace every 1 by 5*/

--------------------------------------------------------------------------------

THE STORAGE OF EXCEPTION OBJECTS

Consider the following program:


class Exception {};
int main()
{
try
{
throw Exception(); /* where is the exception stored? */
}
catch( Exception & ex) /* catch by reference */
{}
}
The handler catches the exception by reference, not by value. Catching exception by reference is better than catching by value because it's more efficient and it avoids slicing. However, a programmer who read this recommendation was intrigued: "If the exception object is created on the stack, why doesn't it get destroyed in the process of stack unwinding, as do other automatic (nonexception) objects?"

Although the C++ standard doesn't specify where exceptions are stored in memory, the general approach among compiler vendors is to use a special stack for exceptions. This stack is not affected by the stack-unwinding process. Therefore, when an exception is thrown from a function and that function exits, the exception object remains alive until the handler that caught it terminates.

--------------------------------------------------------------------------------

THE UNDERLYING REPRESENTATION OF POINTERS TO MEMBERS

Although pointers to members behave very much like ordinary pointers, behind the scenes, they aren't necessarily represented as pointers. In fact, a pointer to a member is usually a struct that can contain up to four members. This is because pointers to members don't support only ordinary member functions; they also support virtual member functions, member functions of objects that have multiple base classes, and member functions of virtual base classes. The simplest member function can be represented as a set of two pointers: one holding the physical memory address of the function, and a second pointer that holds that pointer. However, in cases like a virtual member function and multiple inheritance, the pointer to a member must store additional information; hence, its underlying structure gets larger and more complex. Therefore, you cannot cast pointers to members to ordinary pointers, nor can you safely cast a pointer to a member of one type to another; in both cases, the results are undefined.

To get an idea of how your compiler represents pointers to members, you can measure their size. In the following example, the pointers to data members have different sizes and, hence, different representations (of course, the results may vary across platforms and compilers):


struct A
{
int x;
void f();
};
int A::*pmi = & A::x;
void (A::*pmf)() = & A::f;
int n = sizeof (pmi); /* 8 byte on my machine */
int m = sizeof (pmf); /* 12 bytes on my machine */

--------------------------------------------------------------------------------

THE USE OF VIRTUAL DESTRUCTORS

By design, std::string, std::complex, and all STL containers do not have a virtual destructor or virtual member functions. The lack of a virtual destructor means one thing: This class shouldn't be used as a base class. Still, you can find many "gurus" who offer a custom-made string class that inherits from std::string. This is a bad idea, though. To see why, consider the following program:

#include < string >
#include < iostream >
using namespace std;
int destr = 0; /* destructor counter */

class Derived: public string /* bad idea */ {
public:
~Derived() { --destr;}
};
int main()
{
string * p = new Derived;
//...use p
delete p; /*undefined behavior*/
cout < < destr; /*surprise! n == 0*/
}

Class Derived is inherited from std::string. It has a destructor, which decrements the value of destr. Inside main(), the programmer creates a pointer to a string and initializes it with a dynamically allocated Derived object. The next line is problematic. Seemingly, the delete expression destroys the Derived object and invokes its destructor. However, if you examine destr, you will discover that its value remains unchanged. This is because Derived's destructor was never called! Consequently, the program's behavior is undefined. To conclude, deriving from std::string--or any class that doesn't have a virtual destructor--is bad and dangerous.

--------------------------------------------------------------------------------

USE EXPLICIT UNSIGNED TO INDICATE BINARY DATA

Whether char is signed or unsigned is compiler-dependent. Some compilers treat the array

char arr[100];

as 100 unsigned characters; others treat it as 100 signed characters. The unsigned quality of char is important when you're working with raw binary data. To ensure portability, always add an explicit unsigned qualifier when you declare char variables that store binary data:

unsigned char mp3_clip[KB*KB]; /* 1 Mb buffer*/

horizontal rule

USES OF AN ASSOCIATIVE ARRAY

An associative array stores pairs of values--one serves as the key and the other is the associated value. STL's map is an example of an associative array. In the following example, it stores pairs of strings and enumerated values. The string is the key whose associated value is an int. Some symbolic debuggers associate enum values with their textual description using a technique similar to the following:


#include < utility >
#include < map >
#include < string >
using namespace std;
enum directions {up=0, down=1};

int main()
{
/*create a pair*/
pair < string, int > Enumerator(string("down"), down);
map < string, int > mi; /*create a map*/
mi.insert(Enumerator); /*insert the pair element*/
int n = mi["down"]; /*get associated value of "down"*/
}
The header < utility > defines the template pair < class Key, class Value >, which serves as a map element. After creating a pair instance called Enumerator, and initializing it with the string value "down" and its associated integral value, we create a map. We then insert Enumerator to the map. Finally, we retrieve the integral value associated with the key "down" and store it in n.

horizontal rule

USING OBJECTS WITH NO DEFAULT CONSTRUCTOR AS CLASS MEMBERS

Suppose you have a class called A that doesn't have a default constructor, and you need an instance of this class as a member of another class, B:

class A
{
public:
A (int n); /*no default ctor*/
};

When you instantiate an ordinary A object, you pass an argument to its constructor:

A a(3);

However, you can't do that when A is a member of another class:

class B
{
private:
A a(3); /*error*/
public:
B();
};

Instead, use a member-initialization list in the containing class's constructor to pass an argument to the embedded object's constructor:

class B
{
private:
A a; /*no argument here*/
public:
B() : a(3) {} /*pass arg here*/
};

horizontal rule

USING THE SORT() ALGORITHM

The generic algorithm sort() sorts a sequence of elements. Sort() takes two iterators that point to the beginning and the end of a sequence:


#include < algorithm > // for sort()
#include < vector >
using namespace std;

int main()
{
vector < int > vi;
vi.push_back(7);
vi.push_back(1);
vi.push_back(19);
sort(vi.begin(), vi.end() ); /* sort vi; default is ascending order */
}

horizontal rule

VECTORS AND CONTIGUOUS MEMORY

A built-in array is stored on a contiguous block of memory. Because of an oversight, the C++ Standard doesn't officially guarantee that vector elements also occupy contiguous memory. This isn't really a problem because all current STL implementations use contiguous memory for vectors, and the requirement that vectors occupy contiguous memory will be added to the C++ Standard in the next revision.

horizontal rule

VOLATILE CONTAINER OBJECTS

Volatile objects are used in multithreaded applications and applications that map hardware devices into registers. Although you can declare an STL container object with the volatile qualifier, you can't use any of its member functions safely. The problem is that STL containers don't have any volatile member functions. Calling a nonvolatile member function on a volatile object is ill formed:

volatile vector < int > vi;
vi.push_back(1); /* trouble; push_back isn't volatile*/

Because of the attempt to call a nonvolatile member function, some compilers compile this code with warnings; others issue an error message. Calling a nonvolatile member function from a volatile object yields unpredictable results, because the state of the object might be changed by another thread in between.

Is there a way to get around this? No, there isn't. By design, STL containers don't include volatile member functions because of the performance penalty and implementation complexity associated with volatile semantics.

horizontal rule

VOLATILE MEMBER FUNCTIONS

You're probably familiar with const member functions such as


class A
{
public:
int get_x() const;
};
C++ also supports volatile member functions. You declare a member function with the volatile specifier to ensure that it can be called safely for a volatile object. For example:


class B
{
int x;
public:
void f() volatile; /* a volatile member function */
};

int main()
{
volatile B b; /* a volatile object */
b.f(); /* call a volatile member function safely */
}
Calling a non-volatile member function for b, which itself is a volatile object, is unsafe (some compilers will flag this call as an error). To ensure that f() can be called safely for a volatile object, it's declared volatile, too.

horizontal rule

WHERE DEFAULT ARGUMENTS CAN APPEAR

Default arguments can be specified only in the parameter list of a function declaration or in a template-parameter. This means that default arguments cannot appear in declarations of pointers to functions, pointers to member functions, references to functions, or typedef declarations. For example:


void f( int n = 0); /* OK */

/* the following declarations are all ill-formed */
void ( & rf) (int n = 0) = f; /* reference to function */
void ( * pf) (int n = 0) ; /* pointer to function */
typedef void ( * pfi) (int n = 0); /* typedef */

horizontal rule

WHO IS THIS

In the body of a nonstatic member function, the keyword "this" is a non-lvalue expression whose value is the address of the object for which the function is called. By contrast, in pre-standard C++, "this" was a pointer. The difference between a real pointer and a "non-lvalue expression whose value is the address of the object" might seem like hair-splitting. However, the distinction between the two is important because the new definition guarantees that programmers can't assign a new value to "this" (assigning to "this" was valid programming practice in the preliminary stages of C++). In other words, you can think of "this" as a function returning the address of its object, rather than the pointer itself.

horizontal rule

ZERO INITIALIZATION

To zero-initialize an object of type T means that the memory storage occupied by the object is set to binary zeros. More precisely, if T is a built-in data type, an enumeration type, or a pointer type, the storage is set to the value of 0 converted to T. For aggregates and class objects, zero-initialization means one of the following:

* For a class type, the storage for each data member and each base-class subobject is zero-initialized.
* For a union type, the storage for its first data member is zero-initialized.
* For an array type, the storage for each element is zero-initialized.

If T is a reference type, no initialization is performed because there are no null references in C++.

horizontal rule

HIDE FUNCTION POINTER DECLARATIONS WITH A TYPEDEF

Can you tell what the following declaration says?

void (*p[10]) (void (*)() );

It declares p as an array of ten pointers to a function returning void and taking a pointer to another function that returns void and takes no arguments. The cumbersome syntax is nearly indecipherable. However, you can simplify it considerably by using typedef declarations. First, declare a typedef for "pointer to a function returning void and taking no arguments" as follows:

typedef void (*pfv)();

Next, declare another typedef for "pointer to a function returning void and taking a pfv" based on the typedef we previously declared:

typedef void (*pf_taking_pfv) (pfv);

Using the typedef pf_taking_pfv as a synonym for the unwieldy "pointer to a function returning void and taking a pfv," declaring an array of ten such pointers is a breeze:

pf_taking_pfv p[10];

horizontal rule

THE MAXIMAL MUNCH RULE

Every compiler has a tokenizer, which is a component that parses a source file into distinct tokens (keywords, operators, identifiers, etc.). One of the tokenizer's rules is called "maximal munch," which says: Keep reading characters from the source file until adding one more character causes the current token to stop making sense. For example, if the letters c, h, and a have been read and the character that follows is r, the tokenizer will read it and complete the token char. In certain contexts, the maximal munch rule can have surprising effects, though. Consider the following declaration:

vector < stack < int >> vs; /*error*/

The programmer wanted to declare a vector of stacks. However, the tokenizer didn't parse this declaration correctly. Because of the maximal munch rule, the sequence >> is parsed as a single token (the right shift operator) rather than two template argument list terminators. At a later stage, the syntactic analyzer detects that a right shift operator doesn't make sense in this context and the compiler will issue an error message.

To fix this, insert a space between the two >>. The tokenizer treats whitespaces as token terminators. Therefore, it will now parse the following declaration correctly:

vector < stack < int > >; /*now OK*/

horizontal rule

FLOATING POINT LITERALS

In the expression

if (ticket_price > 7.50)

what's the type of the literal 7.50? You might think that it's float. However, the actual type is double. To force the compiler to store a floating point literal as a float rather than double, you have to append the affix f to it:

if (ticket_price > 7.50f)

horizontal rule

IDEMPOTENT TYPE QUALIFIERS

The type qualifiers const and volatile are idempotent. This means that if a type qualifier is included indirectly several times in a type specification (through a typedef or a template parameter), it's treated as if it appeared only once. For example:

typedef const int CI;

The typedef CI is a synonym for const int. Now suppose we declare another typedef based on CI:

typedef const CI CCI;

CCI is a synonym for const CI, which is actually const const int. The compiler ignores the redundant const and treats CCI as a synonym for const int. For example:

int f(CCI n); /*fine, f takes a const int*/

Remember that this rule applies to indirect inclusion of a type qualifier; direct repetitions are illegal:

void f(const const int); /*ill-formed*/

In C99, the newly approved C standard, this restriction was relaxed and even direct repetitions are allowed.

horizontal rule

THE LOCATION OF TEMPLATE DEFINITIONS

Normally, you declare functions and classes in an .h file and place their definition in a separate .cpp file. With templates, this practice isn't really useful because the compiler must see the actual definition (that is, the body) of a template, not just the declaration, when it instantiates a template. Therefore, it's best to place both the template's declaration and definition in the same .h file. This is why all STL header files contain template definitions.

In the future, when compilers support the "export" keyword, it will be possible to use only the template's declaration and leave the definition in a separate source file.

horizontal rule

A FREE XML PARSER

XML4C is a validating XML parser written in C++. It enables an application to read and write XML data. XML4C is compliant with the XML 1.0 specification. Source code, samples, and API documentation are provided with the parser, for free. You can download it (registration is required) from

http://www.alphaworks.ibm.com/tech/xml4c

CORRECTION
Some of our recent tips contained errors in HTML coding. The HTML errors caused our C++ code snippets to display improperly. We apologize for the errors. To view correct versions of these tips, please visit the archive at

http://www.topica.com/lists/tipworld-cplusplus/read

horizontal rule

A GENERIC CALLBACK DISPATCHER

You can create a generic callback class template to automate member functions' callback. Such a template takes the class whose member function is to be called as its first parameter. The second parameter is a pointer to that class's member function. The trick is that you base the second parameter on the first one, as follows:

template < class T, void (T::*F)() > class callback {/**/};

The implementation of the template is relatively simple: It has a reference to T, which is the class whose member function is to be called, a constructor and a member function called execute():

template < class T, void (T::*F)() >
class callback
{
public:
callback(T& t) : object(t) {}/*assign actual object to T*/
void execute() {(object.*F)();}/*launch callback function*/
private:
T& object;
};

To call a member function through a pointer to member, you must have a reference or pointer to the actual object. This is why the template has a T& as a member. Now suppose you want to use the callback template to execute a member function of class A:

class A
{
public:
void f();
};

You can't use a variable as the address of the member function. Instead, use the & operator to take the function's address. Finally, pass the object whose member function you want to be called as the argument for the template object:

int main()
{
A a; /*first, create an object*/
callback < A, &A::f > c(a); /*instantiate template*/
c.execute(); /*invoke a.f()*/
}

You can use the callback class template with any class type, as long as the member function called has the same signature.

horizontal rule

A PORTABLE DATA FORMAT

Data types larger than the size of a byte aren't portable because of the different endian-ordering of hardware architectures. Therefore, passing an int in its binary form from an Intel-based machine to a UNIX machine or vice versa requires that you reverse the integer's byte before the target machine can read it. Sometime, reversing the byte ordering is too expensive or too complicated. Worse yet, there are several ordering options: little endian, big endian, middle endian, and so on. It's not always possible to predict the exact byte ordering on the target machine.

Is there a workaround? Yes, there is. Pass all data variables in their textual representation. For example, instead of passing the integer 0 as a sequence of four bytes, pass the string 0. Another example: Instead of passing the integer 32 as a sequence of the four bytes 0x0, 0x0, 0x0, 0x20, pass the literal string 32 instead. Char arrays are portable, and they do not require order reversing.

horizontal rule

A REFERENCE TO A REFERENCE IS ILLEGAL

What is wrong with this code snippet?

#include &lt; string &gt;
#include &lt; list &gt;
using std::string;
using std::list;

class Node {/*..*/};

int main()
{
list &lt; node & &gt; ln; /* error */
}

If you try to compile this example, your compiler will issue numerous compilation errors. The problem is that list &lt; T &gt; has member functions that take or return T&. In other words, the compiler transforms &lt; node & &gt; to &lt; node && &gt;. Since a reference to a reference is illegal in C++, the program is ill formed. As a rule, you should instantiate templates in the form of list &lt; node &gt; and never as list &lt; node & &gt;.

horizontal rule

A SHORTHAND FOR INTERNATIONALIZATION

The software industry uses the term I18N as an abbreviation for "internationalization." The idea is that I18N begins with an i, ends with an n, and has 18 characters in between. This way, one avoids the unwieldy term and its different spelling conventions (internationalisation in British English). Likewise, "localization" is abbreviated to L10N. When you're consulting your compiler's online help or run a query on a search engine regarding internationalization and localization of C++ apps, try these abbreviations.

horizontal rule

ACCESSING MEMBERS WITH STATIC MEMBER FUNCTIONS

A static member function doesn't take an implicit "this." Therefore, it can't access any other members of its class unless they are also static. Sometimes you have no choice but to use a static member function, but you still need to access other members of the class from that function. There are two solutions. One solution is to declare these members static, so that the static member function can access them directly. For example:

class Singleton
{
public:
static Singleton * instance();
private:
Singleton * p;
static Lock lock;
};

Singleton * Singleton::instance()
{
if (lock.used) /*OK, lock is a static member*/
//..
}

Alternatively, pass the static member function a reference to the object so that it can access its members:

void C::func( C & obj) /* a static member function*/
{
int n = obj.get_x(); /*access a member through reference*/
}

horizontal rule

ADDING OBJECT-ORIENTED FUNCTIONALITY TO A POD TYPE

Sometimes, legacy code that contains Plain Old Data (POD) types can use an object-oriented face-lift, such as adding a constructor or member functions. Instead of adding the new functionality into the POD struct itself, it's better to derive a new class from it and add the new object-oriented functionality--member functions, constructor, and destructor--to the newly derived class. Leaving the POD struct intact will ensure binary compatibility with other code components that still use the original struct.

horizontal rule

ARGUMENTS AND PARAMETERS

ARGUMENTS AND PARAMETERS The terms "argument" and "parameter" are often used interchangeably in the literature, although the C++ Standard makes a clear distinction between the two.

An argument is one of the following:

an expression in the comma-separated list in a function call
a sequence of one or more preprocessor tokens in the comma-separated list in a macro call
the operand of a throw-statement
an expression, type, or template-name in the comma-separated list in the angle brackets of a template instantiation.
An argument is sometimes called an "actual parameter."

A parameter is one of the following:

an object or reference that is declared in a function declaration or definition
an identifier between the parentheses immediately following the macro name in a macro definition
a template-parameter
A parameter is sometimes called a "formal parameter."

The following example demonstrates the difference between a parameter and an argument:

void func(int n, char * pc); /* n and pc are parameters */
template &< class T &> class A{}; /* T is a a parameter */

int main()
{
char c;
char *p = & c;
func(5, p); /* 5 and p are arguments */
A &< long &> a; /* 'long' is an argument */
A &< char &> another_a; /* 'char' is an argument */
return 0;
} CORRECTION: A REFERENCE TO A REFERENCE IS ILLEGAL Part of the code for yesterday's tip came out incorrectly. Here is the tip as it should have appeared yesterday. We apologize for any incovenience.

What is wrong with this code snippet?

#include &< string &>
#include &< list &>
using std::string;
using std::list;

class Node {/*..*/};

int main()
{
list &< node & &> ln; /* error */
}

If you try to compile this example, your compiler will issue numerous compilation errors. The problem is that list &< T &> has member functions that take or return T&. In other words, the compiler transforms &< node & &> to &< node && &>. Since a reference to a reference is illegal in C++, the program is ill formed. As a rule, you should instantiate templates in the form of list &< node &> and never as list &< node & &>.

horizontal rule

ARRAYS OF LITERAL STRINGS

Does your compiler accept the following code?

char s[3]="abc"; /* illegal; no place for '\0'*/

It shouldn't. The standard requires that the size of a char array initialized with a literal string be sufficiently large to contain a terminating null. The declaration should be flagged as a compilation error because s has three elements instead of four. However, many C++ compilers still follow the array rules of C, which permit char arrays without a terminating null.

horizontal rule

ASSIGNING A SPECIFIED MEMORY ADDRESS TO A POINTER

In low-level programming and hardware interfaces, you often need to assign a pointer to a specific physical address. To do that, cast the address value using the reinterpret_cast operator. Here's an example:

void *p;
/* assign address 0x5800FF to p*/
p = reinterpret_cast < void* > (0x5800FF);

horizontal rule

ASSIGNING INTEGERS TO ENUM VARIABLES

C and C++ differ in their handling of enum types. While C allows you to assign a plain int to an enum variable, C++ doesn't. Therefore, a C compiler will accept the following code while a standard compliant C++ compiler won't:

enum Direction (West, North East, South};
Direction d;
d = 1; /* OK in C, d equals 'North' */

C++ has stricter type safety rules. A standard-compliant C++ compiler will reject the assignment of 1 to d. You have to use an explicit cast for this to work, or better still, always assign an enumerator to an enum variable:

d = static_cast < Direction > (1); // fine
d = East;

horizontal rule

AVOID ASSIGNMENTS INSIDE AN IF CONDITION

An assignment expression can appear inside an if condition:

if (x = getval() )
{
/* do something */
}

The if condition is evaluated in two steps: First, the unconditional assignment to x takes place. Then, x is checked. The if-block is executed only if x's value (after the assignment) isn't 0. Although this technique can save you a few keystrokes, it's highly dangerous and should be avoided. The problem is that one can easily mistake == for = or vice versa. For this reason, several compilers issue a warning message if you place an assignment expression inside an if condition, to draw your attention to a potential bug. If you need to assign a value and test the result, separate these steps into two distinct statements:

x = getval();
if (x)
{
/* do something */
}

This way, you document your intention more clearly and avoid this potential bug.

horizontal rule

AVOIDING MEMORY FRAGMENTATION

Often, applications that are free from memory leaks but frequently allocate and deallocate dynamic memory show gradual performance degradation if they are kept running for long periods. Finally, they crash. Why is this?

Recurrent allocation and deallocation of dynamic memory causes the heap to become fragmented, especially if the application allocates small memory chunks. A fragmented heap might have many free blocks, but these blocks are small and noncontiguous. To demonstrate this, look at the following scheme that represents the system's heap. Zeros indicate free memory blocks, and ones indicate memory blocks that are in use:

100101010000101010110

The above heap is highly fragmented. Allocating a memory block that contains five units (that is, five zeros) will fail, although the system has 12 free units in total. This is because the free memory isn't contiguous. On the other hand, the following heap has less free memory, but it's not fragmented:

1111111111000000

What can you do to avoid heap fragmentation? First, use dynamic memory as little as possible. In most cases, you can use static or automatic storage or use STL containers. Second, try to allocate and deallocate large chunks rather than small ones. For example, instead of allocating a single object, allocate an array of objects at once. As a last resort, use a custom memory pool.

horizontal rule

BINDING A REFERENCE TO AN RVALUE

Binding a reference to an rvalue is allowed as long as the reference is bound to a const type. The rationale behind this rule is straightforward: you can't change an rvalue; only a reference to const ensures that the program doesn't modify an rvalue through its reference. In the following example, the function f() takes a reference to const int:

void f(const int & i);
int main()
{
f(2); /* OK */
}

The program passes the rvalue 2 as an argument to f(). At runtime, C++ creates a temporary object of type int with the value 2 and binds it to the reference i. The temporary and its reference exist from the moment f() is invoked until it returns; they are destroyed immediately afterward. Note that had we declared the reference i without the const qualifier, the function f() could have modified its argument, thereby causing undefined behavior.

The same rule applies to user-defined objects. You may bind a temporary object only to a const reference:

struct A{};
void f(const A& a);
int main()
{
f(A()); /* OK, binding a temporary A to a const reference*/
}

horizontal rule

BLOCKING FURTHER INHERITANCE OF A CLASS

To block a class from being derived any further, declare its constructor private. Next, add a public static member function Instance() that creates an instance of this class and returns its address. Calling Instance() is the only way to create instances of such a class, because the constructor is otherwise inaccessible. Here's an example:

class A
{
private:
A();
public:
static A* Instance(){ return new A;}
};

int main()
{
A *pa = A::Instance();
/*..use pa*/
delete pa;
}

horizontal rule

BOOLEAN LITERALS

The Boolean literals "false" and "true" evaluate to 0 and 1, respectively. In other programming language, true and false may have different values. Visual Basic, for instance, defines false as -1 and true as 1. C++ programmers who write code that interfaces with other languages sometimes discover this fact the hard way. Therefore, don't assume that "true" and "false" have the same representation in all languages and environments.

horizontal rule

C++ NAMING CONVENTIONS

If you're looking for a "house style" of naming conventions, you can adopt the one used in Standard C++. It uses all lowercase letters for identifiers, and underscores as word separators. According to this naming convention, a function that cleans a stack, for example, would be named

void clean_stack(); /* ANSI/ISO C++ naming style */

rather than

void CleanStack(); /* MFC style */

Class names in the Standard Library follow this convention too:

class std::type_info {/*..*/}; /* class name */
class std::vector&lt; /**/ &gt; {/*..*/}; /* class template */

Even constants have all lowercase names:

ios::binary

Remember that the naming convention you pick isn't crucial; what's important is that you use it consistently.

horizontal rule

CACHE THE RESULT OF COMPLEX COMPUTATIONS

The following loop is very inefficient:

for (int j=0; j < strlen(s); ++j)
{
/* .. do something*/
}

On each iteration, it invokes the function strlen(). You can improve this loop's performance by caching the result of strlen() and using the cached value in the loop:

const int length = strlen(s); /*cache the result*/
for (int j=0; j < length; ++j) /*use cached value*/
{
/* .. do something*/
}

horizontal rule

CALCULATING THE POWER OF TWO NUMBERS

The Standard Library provides the pow() functions (declared in < cmath >), which takes two arguments of type double:

double powl(double x, double y);

pow() returns the value calculated of x to the power of y. For example:

double x=10, y=4;
double result=pow(x, y); /*10000*/

horizontal rule

CALCULATING THE SIZE OF AN INCOMPLETE ARRAY

An incomplete array declaration can appear in a function's parameter list. For example:

int count(const char s[]);

The declaration of s doesn't include the array's size. Can count() use the operator sizeof to calculate s's size? No, it can't. The compiler implicitly transforms an array into a pointer. Therefore, sizeof returns a pointer's size, not an array's size. One way to obtain the array's size is to pass an additional argument that holds the number of elements in the array:

int count(const char s[], int arr_size);

However, a better solution is to pass a vector and call its size() member function:

int cout(const vector &lt; char &gt; & s )
{
return s.size();
}

horizontal rule

CALCULATING TIME DIFFERENCES

To compute the time that elapsed between two dates, use the standard function difftime(). This way, you avoid potential bugs such as leap years or Y2K issues. difftime() takes two variables of type time_t, each representing a date, and returns the number of seconds that elapsed between them. For example:

#include < ctime >
#include < iostream >
using namespace std;
int main()
{
time_t now = time(0); /*current time*/
time_t last_week = now - (60*60*24*7);
double seconds = difftime(now, last_week);
cout << seconds << " have elapsed since last week";
}

horizontal rule

CALLING A FUNCTION BEFORE PROGRAM'S STARTUP

Certain applications need to invoke startup functions that run before the main program starts. For example, polling, billing, and logger functions must be invoked before the actual program begins. The easiest way to achieve this is by calling these functions from a constructor of a global object. Because global objects are conceptually constructed before the program's outset, these functions will run before main() starts. For example:

class Logger
{
public:
Logger()
{
log_user_activity();
}
};
Logger log; /*global instance*/

int main()
{
record * prec=read_log();
//.. application code
}

The global object log is constructed before main starts. During its construction, log invokes the function log_user_activity(). When main() starts, it can read data from the log file.

horizontal rule

CALLING A FUNCTION THROUGH A POINTER

To call a function through a pointer, treat that pointer as if it were the function itself. For example:

#include <cstring>
char buff[10];
void (*pf) (char *, const char *);
pf=strcpy; /*take address of strcpy*/
pf(buff, "hi"); /*call strcpy via pf*/

horizontal rule

CALLING A MEMBER FUNCTION THROUGH A POINTER TO MEMBER

The .* and ->* operators enable you to call a member function without having to know the function's name. Remember to parenthesize the function call as follows:

A a; /*create object*/
void (A::*pmf)() = &A::f; /*create pointer to member*/

/*the two pairs of parentheses are mandatory*/
(a.*pmf)(); /*call function thorough pointer to member*/

Omitting the first pair of parentheses, as in the following example, is a common mistake:

a.*pmf(); /*error, missing parentheses*/

Although some compilers accept it for an unknown reason, this is not the correct C++ syntax.

horizontal rule

CALLING OVERLOADED OPERATORS EXPLICITLY

The overloaded operator mechanism is "syntactic sugar" for ordinary function calls. You can always use the explicit name of an overloaded operator to resolve ambiguities and document your intention explicitly. For example, the statement

cout << "hello world";

can be rewritten as follows:

cout.operator<<("hello world");

Instead of using the operator's sign directly, you can use a combination of the keyword 'operator' followed by the operator's sign and its argument list. Another example: Instead of writing

string s;
bool empty;
empty = (s=="");

you can write

empty = s.operator==(""); /* compare *p to an empty string*/

Although you wouldn't normally use this unwieldy form, you should be familiar with this notation because some compilers and linkers issue error messages that contain the explicit operator function name.

horizontal rule

COMMA-SEPARATED EXPRESSIONS

An expression may consist of one or more subexpressions separated by commas. For example:

if(++x, --y, cin.good()) /*three expressions*/

The if condition contains three expressions separated by commas. C++ ensures that each of the expressions is evaluated and its side effects take place. However, the value of an entire comma-separated expression is the result of the rightmost expression. Therefore, the if condition above evaluates as true only if cin.good() returns true. Here's another example of a comma expression:

int j=10;
int i=0;
while( ++i, --j)
{
/*..repeat as long as j is not 0*/
}

horizontal rule

COMPUTING THE MAXIMUM OF THREE VALUES

The std::max() template takes two values and returns the highest. What if you need to calculate the maximum of three values? Don't write a special function template for this purpose. Instead, apply max() to two arbitrary values of the three, and then apply max() once again to the result and the third value:

int l=10, m=11, n=7;
int highest = max(l,max(n,m));

horizontal rule

CONSTANT EXPRESSIONS

C++ requires the use of integral constant expressions in various contexts; for example, when you define arrays' sizes, case labels, bit-field sizes, and enumerator initializers. An integral constant expression is one of the following:

* A numeric literal, as 100 in

x < 100;

* Enumerators such as up and down in the following enum type:

enum state {up, down};

* Const variables or static data members of integral or enumeration types initialized with constant expressions--for example, x in this definition:

const int x = 0;

* Non-type template parameters of integral or enumeration types:

S < 100 > s;

* Finally, a sizeof expression:

sizeof (int);

horizontal rule

COPYING FILES

To copy the contents of one file into another, use the fstream classes. First, open the source file using an ifstream object, then create a target file using ofstream, and finally, read the source into the target using the rdbuf() member function:

#include < ftsream >
int main()
{
std::ifstream in ("oldfile.txt"); /*open original file*/
std::ofstream out("newfile.txt"); /*open target file*/
out << in.rdbuf(); /*read original file into target*/
out.close(); /*explicit close, optional*/
in.close();/*explicit close, optional*/
}

Note that you don't have to call the close() member function because the fstream objects do that automatically when their destructors are invoked. However, you want to call close() if other parts of the program access the files before the fstream objects go out of scope.

horizontal rule

COUNTING "LIVE" INSTANCES OF A CLASS

Counting the number of instances of a certain class is often necessary in debugging and performance tuning. To do that, declare a static data member as a counter, and have the constructor and copy constructor increment it and the destructor decrement it. Finally, add a static member function that returns the counter's value:

class A
{
private:
static int count;
public:
A() { ++count;}
A(const A& rhs) { ++count;}
~A() {--count;}
public:
static int get_count() {return count;}
};

int A::count; /*static member definition*/

Because static members are automatically zero-initialized, you can call this function and get the correct results even if no instance of that class exists:

int main()
{
int n = A::get_count(); // 0
A a;
n = A::get_count(); // 1
A * p = new A(a);
n = A::get_count(); // 2
delete p;
n = A::get_count(); // 1
}

horizontal rule

CREATING IMMUTABLE CONSTANTS

Although usually I don't recommend using macros in C++ code, there are a few exceptions. One such exception is when you want to create a truly immutable constant. Consider this constant:

const int MAX=512;

Seemingly, MAX's value can't be changed because it's const. However, on some platforms, a brute-force cast can change its value. For example:

int *p = const_cast < int * > ( & MAX);
*p= 100; /*attempt to change MAX through a pointer*/

Any attempt to change a const object causes undefined behavior. However, some implementations let this hack pass unnoticed. To disable it altogether, you can use a macro instead of a const object:

#define MAX 512

This time, MAX can't be changed.

horizontal rule

DECIDING IF LEARNING C IS A WASTE OF TIME

"I'm learning C at school/college/from a book. Am I wasting my time learning this language? Should I be learning C++ instead?" is a frequently asked question.

Although I usually recommend learning C++ right from the start, learning C is definitely not a waste of time. C is still a very useful, widely used, and highly capable language. It's been around for 27 years now and will remain one of the most popular programming languages in the foreseeable future. Consider these facts:

C is the language in which every modern operating system is written: All Windows flavors, including the recently shipped Win2000, UNIX, Linux, BeOS, and others are coded in C, as are many APIs and standards, such as POSIX.
C is practically the only high-level language used in real time and embedded code.
The underlying engines of many Web browsers, Web servers, virtual machines, device drivers, and databases are written in this language.
C learners benefit from other advantages: The language is compact and easy to learn. Once C learners become familiar with the concepts of functions and pointers, they can turn into productive C programmers. For all these reasons and others, C still matters.

horizontal rule

DECLARING A TEMPLATE SPECIALIZATION AS A FRIEND OF A CLASS TEMPLATE

You can declare a template specialization as a friend of a class template. In the following example, the class template Vector declares the specialization C < void* > as its friend:

template < class T > class C{/*...*/};
template < class T > class Vector
{
public:
//...
friend class C < void* >; /*other specializations of C are NOT friends of Vector*/
};

Each specialization of Vector has the specialization C < void* > as a friend. However, all other specializations of C, such as C < int >, are not friends of Vector.

horizontal rule

DECLARING VARIABLES INSIDE AN IF-CONDITION

C++ allows you to declare variables just before their use rather than at the top of the enclosing block. For example, you may declare a variable inside the condition of an if-statement:

class Base {/*..*/};
class Derived: public Base {/*..*/};
void func (Base & b)
{
/* pd is declared inside if-condition*/
if ( Derived *pd = dynamic_cast &lt; Derived* &gt; ( & b) )
{
/*dynamic_cast was successful; use pd here*/
return;
}/*pd goes out of scope at this point*/
/*otherwise dynamic_cast failed; variable pd is not in scope*/
}

The advantage of declaring the pointer pd locally is obvious: It's always initialized properly, and it isn't visible to other parts of the program that shouldn't use it.

horizontal rule

DEEP COPYING AND SHALLOW COPYING

The terms "deep copy" and "shallow copy" refer to the way objects are copied during the invocation of a copy constructor or assignment operator. In a deep copy (also called "memberwise copy"), the copy operation respects object semantics. For example, copying an object that has a member of type std::string ensures that the corresponding std::string in the target object is copy-constructed from the target's string object:

class A
{
string s;
};
A a;
A b;
a=b; /*deep copy*/

When assigning b to a, the compiler-generated assignment operator of class A first invokes the assignment operator of class std::string. Thus, the members a.s and b.s are well defined. On the other hand, a shallow copy (also called a "bitwise copy") simply copies chunks of memory from one location to another. A memcpy() operation is an example of a shallow copy. Because memcpy() does not respect object semantics, it won't invoke the copy constructor of an object. Therefore, you should never use memcpy() to copy objects. Use it only when copying POD (Plain Old Data) types: ints, floating point numbers, and dumb structs.

horizontal rule

DELETING A FILE

The standard function remove() deletes a file. It takes one argument of type const char* that contains a filename. You can provide a full path as a filename. On success, remove() returns 0. A non-zero return code indicates an error. For example:

#include < cstdio >
#include < iostream >
using namespace std;
int main()
{
string name;
// ...get file name
int stat=remove(name.c_str());
if (stat)
cout << "failed to delete file";
}

horizontal rule

DELETING MULTIDIMENSIONAL ARRAYS

You can allocate a multidimensional array using new as follows:

class A
{
public:
int j;
/*constructo, destructor...*/
}

int m;
A (*pa)[2][2]=new A[2][2][2]; /*three dimensional array*/
m=pa[0][0][0].j; /*access member of array's first element*/
m=pa[1][1][1].j; /*access member of array's last element*/

The program allocates a three-dimensional array of A's called pa. How do you delete a dynamically allocated multidimensional array? It's simple: No matter how many dimensions the array has, you always use delete[] to destroy it:

delete[] pa;

horizontal rule

DIFFERENCES BETWEEN POSTFIX AND PREFIX OPERATORS

The built-in ++ and -- operators can appear on both sides of their operand:

int n=0;
++n; /*prefix*/
n++; /*postfix*/

You probably know that a prefix operator first changes its operand before taking its value. For example:

int n=0, m=0;
n = ++m; /*first increment m, then assign its value to n*/
cout << n << m; /* display 1 1*/

In this example, n equals 1 after the assignment because the increment operation took place before m's value was taken and assigned to n. By contrast,

int n=0, m=0;
n = m++; /*first assign m's value to n, then increment m*/
cout << n << m; /*display 0 1*/

In this example, n equals 0 after the assignment because the increment operation took place after m's original value was taken and assigned to n.

To better understand the difference between postfix and prefix operators, examine the disassembly code generated for these operations. Even if you're not familiar with assembly languages, you can immediately see the difference between the two--simply notice where the inc (increment) assembly directive appears:

/*disassembly of the expression: m=n++;*/
mov ecx, [ebp-0x04] /*store n's value in ecx register*/
mov [ebp-0x08], ecx /*assign value in ecx to m*/
inc dword ptr [ebp-0x04] /*increment n*/

/*disassembly of the expression: m=++n;*/
inc dword ptr [ebp-0x04] /*increment n;*/
mov eax, [ebp-0x04] /*store n's value in eax register*/
mov [ebp-0x08], eax /*assign value in eax to m*/

horizontal rule

DIFFERENCES IN INITIALIZATION RULES BETWEEN C AND C++

Consider the following program:

int func();
main()
{
int n = 0;
int arr [2] = { func(), n}; /* ok in C++, not in C */
}

A C compiler will reject the declaration of arr because the initialization list doesn't contain constant expressions. By contrast, a C++ compiler will accept it without a hitch. You're probably thinking that this isn't an issue because you're using a C++ compiler anyway. However, many C++ compilers rely on a source file's extension to determine whether the code therein should be treated as C or C++. Usually, a .c extension invokes the C compiler, whereas a .cpp extension invokes the C++ compiler.

Because the rules regarding initialization are somewhat different in both languages, the program above may or may not compile, depending on the file extension used. In C, the initializer list must contain constant expressions exclusively. In C++, you can use any valid expression as an initializer, including a function's return value or a previously declared variable. Therefore, when porting legacy code, remember to check the file extensions as well. (Note: The recently approved C99 now complies with the C++ initialization rules in this regard. Thus, the array above is well formed in C99 but not in older versions of the C standard.)

horizontal rule

DIFFERENCES PROTOTYPES OF STANDARD FUNCTIONS

A small number of functions from the Standard Library have different signatures in C and C++. These functions are: strchr(), strpbrk(), strrchr(), strstr(), and memchr() as well as their wide-character counterparts: wcschr(), wcspbrk(), wcsrchr(), wcsstr(), and wmemchr(). They are declared in the standard header <string.h> (or < cstring > in C++). Let's look at strstr(). While in C, strstr() has the following prototype:

char* strstr(const char* s1, const char* s2); /*ANSI C*/

In C++, this function has two different prototypes that are incompatible with the C version:

char* strstr(char* s1, const char* s2); /*ANSI C++*/
const char * strstr(const char* s1, const char* s2); /*ANSI C++*/

Another example: the function strpbrk(). In C, it has this signature:

char* strpbrk(const char* s1, const char* s2); /*ANSI C*/

In C++, it has two different signatures:

char* strpbrk(char* s1, const char* s2); /*ANSI C++*/ const char* strpbrk(const char* s1, const char* s2); /*ANSI C++*/

Can you see the pattern here? The C function takes "const X *" and returns "X *" whereas C++ defines two functions, one taking "X *" and returning "X *", and another taking "const X *" and returning "const X *". Pay attention to these subtle differences when you port code from C to C++ or vice versa.

horizontal rule

DIFFERENT INITIALIZATION FORMS OF OBJECT MEMBERS

Consider the following class:

class A
{
public:
A(int size);
private:
int size;
};

You can initialize the member 'size' in at least three forms:

A::A (int size) : size (size) { }
A::A (int size) { this->size = size; }
A::A (int size) { A::size = size; }

All of these forms are legal and portable. However, the first one is preferable because the member initialization notation explicitly indicates that you're initializing a data member.

As for using identical names for the data member and the argument, the compiler is clever enough to distinguish between the two correctly. However, to avoid confusing a human reader, you may consider different names, as in

A::A (int sz) : size (sz) { }

horizontal rule

DISABLING COPYING AND ASSIGNMENT OF AN OBJECT

To block copying and assignment of a class object, explicitly declare its assignment operator and copy constructor private. Don't define them; only declare them. In this example, class A has a public default constructor. In addition, it declares a private copy constructor and assignment operator to ensure that objects of its class can't be copied or assigned to:

class a
{
public:
A(){}
private:
A(const A& ); /*declared; not implemented*/
A& operator=(const A& ); /*ditto*/
};
int main()
{
A a; /*fine, default ctor is public*/
A b (a); /*error, copy ctor is inaccessible*/
A c; /*fine*/
c = a; /*error, assignment operator is inaccessible*/
}

horizontal rule

DUPLICATE NAMESPACE ALIASES

A namespace alias can redefine an existing namespace alias, as long as the new definition refers to the same namespace as the old one. In other words, you're allowed to repeat an existing definition of a namespace alias, but you cannot change the meaning of a previously defined namespace alias. For example:

namespace ATL {/*...*/}
namespace NS=ATL; /*an alias*/
namespace NS=ATL;/*OK, duplicate*/
namespace NS=NS; /*also OK*/
namespace NS=std; /*error*/

This rule enables you to place the same namespace alias definition in every source file of the same project.

horizontal rule

DYNAMIC TYPE AND STATIC TYPE

The type of the most derived object to which an expression refers is said to be the dynamic type of that expression. For example, if p is declared as a pointer to class B but it actually points to an object of class D (where D is derived from B), the dynamic type of the expression *p is D whereas its static type is B:

B * p; /*static type of *p is B*/
p = new B;/*dynamic type of *p is D*/

The same is true for references:

void f(B & b); /* b's static type is B&*/
D d;
f(d); /*argument's dynamic type is D&*/

horizontal rule

ELIMINATING BUFFER OVERFLOWS

Buffer overflows are a fertile source of bugs and malicious attacks. They occur when a program attempts to write data past the end of a buffer. Consider this example:

#include < cstdio >
int main()
{
char buff[15]={0};
printf("enter your name: ");
scanf(buff, "%s"); /*dangerous, length unchecked*/
}

The program reads a string from the standard input (the keyboard). However, it doesn't check the string's length. If the string has more than 14 characters, it causes a buffer overflow as scanf() tries to write the remaining characters past buff's end (remember that one character is always reserved for a null terminator). The result is most likely a runtime crash. On some systems, the users will receive a shell's prompt after the crash. Even if the shell has restricted privileges, end users can still examine environment variables and list the current directory files. Although the program uses C-style I/O operations, such code is still widely used today.

Even if you can't leverage legacy code with a more object-oriented approach, there are still some techniques to avert such overflows. First, always check the array's bounds before writing it to a buffer. If this is impossible--such as when the input is coming from a CGI script--use functions that explicitly limit the number of input characters. For example, instead of using scanf(), use the fgets() function, which reads characters up to a specified limit:

char buff[15] = {0};
fgets(buff, sizeof(buff), stdin); /*read at most 14 chars*/

Additionally, the standard string functions have versions that take an explicit size limit. Instead of strcpy(), strcmp(), and sprintf(), use strncpy(), strncmp(), and snprint(), respectively. Next time, we will see how to avoid overflows using a more object-oriented approach.

horizontal rule

ENHANCING VECTOR'S PERFORMANCE

std::vector allocates storage for its elements on demand. This is advantageous because it frees you from the hassles of manual memory management. However, in real-time environments, frequent reallocation of memory can incur unacceptable overhead. If you know the total number of elements that are to be stored in advance, you can preallocate storage for them at once using the reserve() member function:

int main()
{
vector < Message > msgs;
msgs.reserve(1000); /*make room for 1000 Message elements*/
//...insert elements
}

reserve(n) allocates storage for at least n elements (not bytes), thereby ensuring that the container doesn't reallocate memory as long as the number of elements doesn't exceed n.

horizontal rule

ENUM TYPES ARE ORDINARY TYPES

You can use an enum type as you would use any other type. You can create arrays, allocate it dynamically using operator new, overload operators for it, create pointers, and return it from a function:

enum Dir
{
Up,
Down,
};

Dir get_dir(); /* in a function's return type */

int main()
{
Dir *p=new Dir(Up); /*allocate and initialize enum*/
Dir dir[3]={Up, Up, Up}; /* initialize array */
dir[0]=get_dir();
delete p;
}

horizontal rule

EQUALITY TESTS ON LITERAL STRINGS

It is perfectly legal to use the equality operator to compare literal strings. However, the results might be surprising:

bool eq;
eq = "Mungojerrie" == "Rumpleteazer"; /* What does this code do?*/

Let's parse the following expression carefully:

"Mungojerrie" == "Rumpleteazer";

The literal strings "Mungojerrie" and "Rumpleteazer" are both of type const char *. Thus, the expression checks whether the two memory addresses of the literal strings are identical and returns a Boolean result accordingly. However, the strings themselves are not compared, to the bafflement of the programmer. The right way of comparing literal strings is by using the standard function strcmp(). Alternatively, you can wrap the literal strings in two std::string objects and compare them.

horizontal rule

EQUIVALENCE IN STL

STL containers and algorithms require that you overload operator < for the class type they store as elements. This is necessary for sorting and comparing these elements. STL synthesizes the equality operator from the < operator by using the following construct:

bool operator==( const T & arg1, const T & arg2)
{
return (!(arg1 < arg2)) && (!(arg1 < arg2))
}

In other words, you don't need to define an overloaded version of operator == (or any other relational operator) because STL knows how to generate these operators from operator <. Therefore, you only need to define an overloaded version of operator < for user-defined types.

horizontal rule

ERASING ELEMENTS OF ASSOCIATIVE CONTAINERS

The associative containers std::map, std::multimap, std::set, and std::multiset have three overloaded forms of the member function earse():

class map /*simplified for brevity*/
{
public:
void erase(iterator position); //1
size_type erase(const key_type& x); //2
void erase(iterator first, iterator last); //3
};

The first version takes an iterator pointing at an existing container element and erases it. For example, the following code erases the last element of the container:

employees.erase(employees.rbegin());

An element of an associative container is a pair of two values: a key and its associated value. The second version of erase() enables you to erase an element using its key. For example:

std::map< int, std::string > employees;
//.. fill map
// erase the element whose first member equals 2
employees.erase(2);

The third version of erase() deletes a sequence of elements whose bounds are indicated by the iterators first and last. For example, the following statement erases all the elements of a map:

employees.erase(employees.begin(), employees.end());

horizontal rule

ESCAPE SEQUENCES

Several special characters are represented as escape sequences. An escape sequence begins with a \ (backslash) followed by an alphanumeric character. For example, the \n escape sequence represents the newline character. Note that the two characters of an escape sequence are construed as a single character. Here's a list of C++ escape sequences:

\a /*alert (bell)*/
\b /*backspace*/
\f /*formfeed*/
\n /*newline*/
\r /*carriage return*/
\t /*horizontal tab*/
\v /*vertical tab*/
\\ /*backslash*/
\? /*question mark*/
\' /*single quote*/
\" /*double quote*/

horizontal rule

EXCEPTION SPECIFICATIONS ARE CHECKED AT RUNTIME

A function can specify explicitly what type of exception it might throw by proving an exception specification. Exception specifications are enforced at runtime. Therefore, the following code is perfectly legal:

int f(); /*can throw any type of exception*/
void g(int j) throw(); /*promises not to throw any exception*/
{
int result = f(); /*what if f() throws?*/
}

g() promises not to throw. However, it calls f(), which might throw any type of exception. If f() indeed throws, the exception propagates through g(), thereby violating its guarantee not to throw. Still, the compiler doesn't reject this code because C++ enforces exception specification at runtime.

There are several reasons for the runtime checking policy. In this example, f() could be a legacy C function. It's impossible to enforce a C function to have an exception specification. For this reason and others, C++ exception specifications are enforced at runtime.

horizontal rule

EXTERN FUNCTIONS

Unless explicitly declared static, an ordinary function is implicitly declared extern. For example:

extern void func(int i); /*extern is redundant*/

However, sometimes the extern qualifier is added to a function declaration to document the fact that it has external linkage and can be called from any translation unit--for example:

/* found in < string.h >*/
extern size_t strlen(const char * s);

Do not confuse plain extern with extern "C". extern "C" indicates that an identifier has C linkage rather than C++ linkage, whereas plain extern merely guarantees that the identifier is globally visible.

horizontal rule

EXTRACTING A NUMBER FROM A STRING

Many applications store numbers in strings that also contain text. For example, some databases store dates as strings in the form of "01Jan". You can extract the number from a string by using the standard function strtol() ("string to long"). In this example, strtol() extracts the number 14 from the string "14-Feb" and assigns that number to n:

#include &lt; stdio.h &gt;
int main()
{
char s[] = "14-Feb";
long n = strtol(s, NULL, 10); /* n = 14 */
}

In this example, the radix is decimal. You can specify any radix between 2 through 36 as the third argument of strtol(). Alternatively, you can let strtol() deduce the radix automatically according to the numeric characters it reads from the scanned string by supplying 0 as the radix value.

horizontal rule

FAHRENHEIT TO CELSIUS CONVERSION

Although the Standard Library doesn't define functions that convert Fahrenheit degrees to Celsius degrees and vice versa, you can implement them by yourself. These functions are particularly useful in localization and internationalization projects. You may also find them useful in any international weather report Web site. Here are the two conversion functions:

double to_celsius(double fahr)
{
return 5.0/9.0*(fahr-32.0);
}

double to_fahrenheit(double cel)
{
return 9.0/5.0*cel+32.0;
}

horizontal rule

FASTEST MINIMUM-WIDTH INTEGERS

The header < inttypes.h >, which was first available as a nonstandard extension, recently became an integral part of C99. It defines a set of typedef names called "fastest minimum-width integer types." Each of these names designates an integer type that is usually fastest to operate with among all integer types that have at least the specified width. These integers have the general form of int_fastn_t or uint_fastn_t, where n is the minimum required width. For instance, int_fast32_t is the fastest signed integer type having a width of at least 32 bits. The fastest minimum-width integer types are

int_fast8_t int_fast16_t
int_fast32_t int_fast64_t

Their unsigned counterparts are

uint_fast8_t uint_fast16_t
uint_fast32_t uint_fast64_t

What are they good for? Suppose you need a loop counter that needs no fewer than 16 bits. Ostensibly, you can use a variable of type short. However, under certain RISC architecture, the use of short is less efficient than int. To make sure that the counter's type is always the one with which the CPU operates optimally, use int_fast16_t instead of short. This way, you ensure that on every platform, the compiler uses the fastest integral type that has at least 16 bits:

#include < inttypes.h >
int_fast16_t n;
for (n=0; n < 1000; ++n)
{
//..
}

horizontal rule

FIXING A POTENTIAL BUG IN CTYPE FUNCTIONS

For efficiency reasons, most implementations use bitwise operators rather than relational operators to implement the <cctype> standard functions isalpha(), isdigit(), isprint(), and so on. Bitwise operations work well only when applied to unsigned values. However, on some implementations, char is signed by default. Consequently, these functions may produce incorrect results. To fix this, explicitly cast the argument to unsigned char. This will ensure that these functions produce the correct results, regardless of char's signedness:

char c;
cout << "enter a character";
cin >> c;
bool alpha=isalpha ((unsigned char)c);

horizontal rule

FLOATING-POINT ARITHMETIC MYTHS

In the olden days, the use of floating-point variables imposed significant computation and speed overhead compared to integer arithmetic. For this reason, many optimization guidebooks and IT veterans still use integers instead of floating-point data. However, on some modern processors (such as Pentium), floating type arithmetic is FASTER than integer arithmetic. Therefore, if you want to accelerate a computation-intensive application on such platforms, you should reverse this rule: Use floating-point data instead of integers. Don't get me wrong here--I don't recommend using floating point instead of integers as loop counters, for example. The lesson here is that you should experiment before applying any optimization measures--some of them might turn out to be counterproductive.

horizontal rule

FLOATING-POINT NUMBERS REPRESENTATION

People sometimes complain about the inaccuracy of floating-point arithmetic. To demonstrate the level of floating-point inaccuracy, consider the following program:

#include < iostream >
using namespace std;
int main()
{
float f1 = 2000.58;
float f2 = 2000.0;
cout << f1 - f2;
}

On my machine, this program prints 0.579956 instead of 0.58. More complex calculations yield higher inaccuracy. What is going on here?

Remember that rounding, approximation, and truncation are not the responsibility of C++. Rather, they depend on the particular hardware your machine uses. Furthermore, floating-point numbers are merely an approximation based on the IEEE standard.

On most machines, type float occupies 32 bits, of which 1 bit will be used for the sign representation, 8 bits for exponent, and the remaining 23 bits for the mantissa. Because a mantissa always has the form 1.nnnn... the leading 1 can be dropped so there are actually 24 bits allocated for the mantissa. 24-bit accuracy can have a deviation of %0.0000062 from the original number. For higher accuracy, you can use the type double, which provides 53 bits of mantissa. This is more accurate than 24 bits, but you can never get absolute accuracy with bits.

horizontal rule

FRIENDSHIP AND NESTED CLASSES

When you declare a nested class as a friend of its containing class, place the friend declaration after the declaration of the nested class, not before it:

class A
{
private:
int i;
public:
class B /*nested class declared first*/
{
public:
B(A & a) { a.i=0;}; /*access A's private member*/
};
friend class B;/*friend declaration at the right place*/
};

If you place the friend declaration before the nested class's declaration, the compiler will discard it since the friend class has not been seen yet.

horizontal rule

GETTING A PROGRAM'S NAME

The name of the program's executable file is stored in argv[0] as a null-terminated char array. To access it, declare main() as follows:

int main(int argc, char* argv[])

Even if the application were called without command-line arguments, C++ ensures that at least one argument, namely argv[0], is passed to main(). You can extract it like this:

int main(int argc, char ** argv)
{
cout << argv[0]; /*name of exe file*/
}

horizontal rule

GUIDELINES FOR HANDHELD COMPUTER PROGRAMMERS

Handheld devices and embedded systems revive programming techniques that existed in the olden days, when every byte of memory and every CPU cycle counted. The following guidelines can be useful for programmers who aim at the handheld devices market niche.

Be prepared to resolve chronic out of memory exceptions: Handheld devices do not have a hard disk. Therefore, memory swapping is not an option. Your program should be prepared to handle recurrent memory exhaustion exceptions. To minimize the risks of memory exhaustion, remember to release allocated memory as soon as it's not needed anymore.
Find alternatives to fancy language features: Fancy language features, such as templates, Runtime Type Information, and exception handling, may not be supported on most handheld devices because they impose significant runtime or memory overhead. Your applications should use alternative methods, such as the traditional C-style errno, and virtual functions.
Lower-level frameworks ensure satisfactory performance: While MFC and similar object-oriented framework can simplify the development process, they add an unacceptable overhead in terms of size and speed. It may be useful to consult your API book again. At present, this is the only framework that can provide satisfactory performance.

horizontal rule

GUIDELINES FOR PORTING CODE

Porting software to a different compiler or platform can be very easy or very difficult, depending on several factors. The most important one is the extent to which the software relies on nonportable and compiler-specific features. For example, if your code uses nonstandard keywords such as __closure (in Borland's C++ Builder) and __int64, you will have to replace them with the equivalent keywords supported by the target compiler. Using standard constructs that have the same semantics instead of proprietary keywords is even better.

Porting is more difficult if your app is based on a proprietary framework (such as MFC) that the target platform (such as UNIX) doesn't support. In that case, you need to perform code analysis and separate portable code components such as business logic classes, financial and arithmetic functions, and standard functions and classes from nonportable GUI and system APIs. The portable components can be used as is on the target platform. As for the nonportable components, it's likely that your target platform offers a corresponding set of classes or API functions that you can use instead. For example, instead of the spawn() family of functions used in Win32, you can use the fork() and exec() functions in UNIX and Linux. Sometimes, however, you will have to redesign parts of your app from scratch.

horizontal rule

ABSOLUTE VALUES

The standard functions abs() and labs() (declared in < cstdlib >) return the absolute value of int and long, respectively. You can use these functions in computations that require absolute values. For example:

int begin=0;
int end=100;
int offset=abs(begin-end);

horizontal rule

ALL ABOUT MAIN()

A program must contain a global function called main, which is the designated start of the program. It's illegal to overload main(). However, an implementation must define two versions thereof:

int main()
int main() (int argc, char* argv[])

The return type of main() shall be int. Note that the use of void as main's return value is noncompliant (although many compilers still permit it). In certain implementations, main() takes additional parameters. The additional parameters should appear after the two mandatory parameters. For example:

/* POSIX systems such as Linux and Unix use this form too*/
int main() (int argc, char* argv[], char* env[])

Declaring main() inline or static is ill-formed. Remember that main is not a keyword. Therefore, you may declare variables, member functions, and constants called main.

horizontal rule

ASSIGNING INT TO AN ENUM VARIABLE

C++ doesn't allow you to assign integer value to an enumeration directly:

enum Direction (Up, Down};
Direction dir;
dir=0; /*error, can't assign int to enum type*/

You need to use static_cast to explicitly cast an integer to an enumeration:

dir=static_cast < Direction > (0); /*dir equals 'Up'*/

If the int value is outside the valid ranges of the enumeration, the results are undefined:

dir=static_cast < Direction > (5);/*undefined behavior*/

Therefore, use this technique with caution.

horizontal rule

BLOCKS AND COMPOUND STATEMENTS

A "compound statement" (called a "block") is a sequence of one or more statements enclosed between { and }. For example:

for (int n=0; n<100; ++n)
{
//.. a compound statement
}
void func()
{
//.. a compound statement
}

horizontal rule

CASTING POINTERS TO MEMBERS

C++ allows casting a pointer to member to another pointer to member, as long as the signatures of the two match. However, the results of such a cast are implementation-defined. You have to consult your compiler's manual to make sure that such casts are supported. If your code is to be ported to several target platforms, you shouldn't cast pointers to members at all.

horizontal rule

CHECKING A STREAM'S STATE

The iostream family provides the following member functions and operators for checking a stream's state:

bool good() /*true if no error flag is set*/
bool eof() /*true if eofbit is set*/
bool fail() /*true if failbit or badbit is set*/
bool bad() /*true if badbit is set*/
bool operator!() /*as fail()*/
operator void*() /*returns null if fail() is true*/

good() takes all the stream's flags into account, including eofbit, whereas fail(), operator!(), and operator void*() ignore eofbit. If you want to check whether the stream is in a state of error, use fail(), operator!(), or operator void*(). To check a failure of input operations, call good().

horizontal rule

CREATING A TEMPORARY FILE

The standard function tmpfile() is declared in <csdtio> (formerly <stdio.h>) as follows:

FILE * tmpfile();

It opens a temporary file for read/write operations and returns a pointer to that file. tmpfile() chooses unique filenames to avoid conflicts with any existing files. The temporary files created by tmpname() are automatically deleted when the program terminates.

horizontal rule

DEBUG INFORMATION IN A RELEASE VERSION

It may be tempting to squeeze out an extra ounce of performance by removing all debug information from an application's release version. Even if an application seems stable at your site, mysterious crashes--due to different settings, incompatible libraries, different device drivers, and so on--may occur when it's shipped to a customer. Therefore, many developers recommend leaving minimal debugging info even in the release version so that the runtime environment can print the call stack in a human-readable format. Without this information, you won't have any information about the cause of a crash.

horizontal rule

EMITTING A BEEP

` '\a' is the symbolic escape sequence of the BEL character. To emit a beep from your program, write it to the standard output. For example:

const char BEEP = '\a';
cin << num;
if(num< 1)
{
cout << BEEP << "wrong input";
}

horizontal rule

FACTORIAL FUNCTION

A factorial function is a classic example of recursion. Here's a typical recursive factorial function:

int factorial (int num)
{
if (num==1)
return 1;
return factorial(num-1)*num; /*recursive call*/
}

factorial() calls itself recursively, subtracting 1 from num on each call, until it equals 1. As always, you can use iteration instead of recursion:

int factorial (int num)
{
int result=1;
for (int i=1; i<=num; ++i) result*=i;
return result;
}

The nonrecursive version is slightly faster because it avoids the overhead of recurrent function calls.

horizontal rule

HIDING CUMBERSOME FUNCTION POINTER SYNTAX

Can you tell what the following declaration means?

void (*p[10]) (void (*)());

p is an "array of 10 pointers to a function returning void and taking a pointer to another function that returns void and takes no arguments," but the cumbersome syntax is nearly indecipherable. You can simplify this declaration considerably by using typedefs. First, declare a typedef for "pointer to a function returning void and taking no arguments" like this:

typedef void (*pfv)();

Next, declare another typedef for "pointer to a function returning void and taking a pfv":

typedef void (*pf_taking_pfv) (pfv);

Now declaring an array of 10 such pointers is a breeze:

pf_taking_pfv p[10]; /*equivalent to
void (*p[10]) (void (*)()); but much more readable*/

 

horizontal rule

OLD-STYLE FUNCTION DECLARATIONS

In earlier stages of C, function declarations looked like this:

int func() /*no parameters inside parentheses*/
int a,b; /*instead, parameters were declared here*/
float f; /*another parameter*/
{
/*...function body*/
}

The old-style function declaration had empty parentheses. The parameters were declared after the parentheses and before the opening brace of the function body. Today, this function would be declared as follows:

int func(int a, int b, float f)
{
/*...function body*/
}

The old-style function declaration was never supported in C++. However, it was supported in C for many years, until not long ago. Today, the recently approved C99 standard disallows old-style function declarations even in C. Legacy C code still contain this obsolete style, though. If you're porting legacy C code to C++ or to a C compiler that is C99-compliant, check for old style function declarations and change them.

 

horizontal rule

PRIVATE MEMBER FUNCTIONS

Private class members represent implementation details that shouldn't be accessible to other classes and users. Your class may declare private member functions as well. For example, suppose you have a multimedia player class whose public member functions play(), zoom() and stop(), call internal member functions, or helper functions, that take care of internal operations such as managing memory buffers, threads, and interfaces to sound and video cards. These internal operations are highly specialized and access low-level hardware interfaces. As such, they are likely to change with each new release or port to another platform. Therefore, they are declared private so that other clients don't use them directly.

 

horizontal rule

RETURNING A VALUE FROM A FUNCTION THAT THROWS

 "Can a function that returns something throw an exception and still return a value?" Let's look at a concrete example:

int f()
{
if (something)
throw X();
else
return 0;
}

f() has two exit points: the throw statement and the return statement. On each invocation, f() can exit only from one of these exit points. Syntactically, there is no way that f() can both throw an exception and return a value. Thus, the execution path of a return statement and a throw statement are always mutually exclusive.

At the logical level, there is another reason why it's impossible to return and throw at the same time. When an exception is thrown, it means that the function encountered an irrecoverable error from which it cannot proceed normally, and therefore it can't return a meaningful value. On the other hand, if the function can return a meaningful result, there's no reason for throwing an exception in the first place.

 

horizontal rule

SIGNATURES OF COPY CONSTRUCTORS

For a class called X, a copy constructor can have one of the following forms:

X(X&);
X(const X&);
X(volatile X&);
X(const volatile X&);

Note that the copy constructor's parameter may not be passed by value, nor can you use any other type but a reference. Therefore, the following forms are illegal:

X(X);
X(const X);
X(X*);

A copy constructor cannot have more parameters, unless they have default values:

X(const X&, int n=0); /*OK default value for 2nd parameter*/
X(const X&, int n=0, void *p=NULL); /*OK*/
X(const X&, int n); /*error*/

 

horizontal rule

STATIC_CAST VERSUS REINTERPRET_CAST

The operators static_cast and reinterpret_cast convert an operand's type. However, they aren't interchangeable; static_cast uses the type information available at compile time to perform the conversion, making the necessary adjustments (such as pointer offset calculation, type promotion) between the source and target types. Its operation, therefore, is relatively safe. On the other hand, reinterpret_cast simply reinterprets the bit pattern of a given object without changing its binary representation. Consider this example:

int n=9; double d=static_cast < double > (n);

In this example, we convert a variable of type int to double. The binary representation of these types is very different. To convert the int 9 to a double, static_cast needs to properly pad the additional bytes of d. As expected, the result of the conversion is 9.0. reinterpret_cast's behavior is different:

int n=9;
double d=reinterpret_cast<double & > (n);

This time, the results are meaningless. After the cast, d contains a garbage value. This is because reinterpret_cast simply copied the bit pattern of n into d as is, without making the necessary adjustments.

For this reason, you should use reinterpret_cast sparingly and judiciously.

 

horizontal rule

STORING DYNAMICALLY ALLOCATED OBJECTS IN STL CONTAINERS

Suppose you need to store objects of different types in the same container. Usually you do this by storing pointers to dynamically allocated objects. Instead of using named pointers, insert the elements to the container as follows:

class Base {};
class Derived : public Base{};

vector < Base * > v;
v.push_back(new Derived);
v.push_back(new Base);

This way you ensure that the stored objects can be accessed only through their container. You delete the allocated objects as follows:

delete v[0];
delete v[1];

 

horizontal rule

STREAMS AND POINTERS

Look at the following program. It compiles under every standard-conforming compiler. At first, this may seem surprising because it's impossible to delete an object that is not a pointer:

#include < iostream >
#include < fstream >
using namespace std;
int main()
{
ifstream &i=*new ifstream("myfile.txt");
//...use i
i.close();
delete i; /*should be "delete &i"; still compiles*/
}

Why doesn't the delete statement cause a compilation error? After all, i is not a pointer. The reason is that stream objects have a void* conversion operator that is called implicitly in a context requiring a pointer. This conversion enables you to treat streams as file pointers, similarly to the way you treat FILE* variables in C. The compiler invokes the void* conversion operator in the delete statement. Needless to say, this results in undefined behavior at runtime.

There are two lessons from this. First, beware of accidentally placing stream objects in a context where true pointers are required; the compiler's type-safety mechanisms are turned off in this case and won't protect you from potential bugs such as this one. Second, notice how dangerous conversion operators can be; use them cautiously.

horizontal rule

UNINITIALIZED ENUM VARIABLES

An automatic uninitialized enum variable has an indeterminate value. Uninitialized global and static enum variables have a zero value by default. This means that such an enum variable might have a value that is not in the range of its enumerators. In the following example, the global enum variable st is default initialized to zero. This bug leads to unpredictable behavior at runtime:

enum Stat {good=1, bad=2}; /*no enumerator equals 0*/
Stat st; /*bad, initialized to 0 by default*/
int main()
{
if(st==good) //always false
//...
}

It's best always to initialize enum variables explicitly, thereby avoiding such bugs:

Stat st=good;

 

horizontal rule

VARIABLE LENGTH ARRAYS

In C89 and C++, an array's size must be an integer constant expression so that it can be computed at compile time. In C99, the newly approved ANSI/ISO C standard, this rule was relaxed -- you can now use any integer expression to define an array's size. For example:

int func(int dim)
{
int arr[dim]; /*possible only in C99*/
}

The number of elements in arr may change every time func() is called. Therefore, its size can only be computed at runtime. Such an array is called a variable length array:

int main()
{
int dim;
printf("enter array's size: ");
scanf("%d",&dim);
func(dim);
}

To support variable length arrays, C99 changed the semantics of the sizeof operator. Usually, sizeof expressions are evaluated at compile time. However, when sizeof is applied to a variable length array, the result is evaluated at runtime.

Remember that variable length arrays aren't dynamic arrays -- they can't change their size during their lifetime. They differ from ordinary arrays in that they may have a different size every time they are instantiated. Variable length arrays can only be local. Note that standard C++ doesn't support variable length arrays. However, because many C++ compilers are also C compilers, some of them may support this feature as a nonstandard extension.

horizontal rule

 

Questions?

Just Check out some of our sponsors

Shop at BestPrices.Com!

web server downtime monitoring

HALO Computer Technology

COPYRIGHT 1998 - 2009 All names used are Trademarks of the respective companies

Home ] Up ]

Send mail to CompanyWebmaster  with questions or comments about this web site.
Copyright © 2009 HALO Computer Technology
Last modified: 03/11/09