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

Starting December 1, 2006 Techsinfo.be will no longer be available please update your links to http://techinfo.e2uhosting.net Thank you

Select Any of These

C++

LAST UPDATED: 22 May 2006 17:33:38 +0200

 

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 perfor