|
For best results: this site requires that cookies be enabled for proper operation - see Legal Page for more info
|
|
Select Any of These |
C++LAST UPDATED: 11 March 2009 14:43:18 -0600
RE-THROWING A CAUGHT EXCEPTION A handler that catches an exception can try to fix the problem. Should it fail to do that, it can re-throw the original exception and let another handler fix it. try-blocks can be nested in a hierarchical order, enabling a re-thrown exception from a lower catch-statement to be caught again. A re-throw is indicated by a throw statement without an operand. Example:
#include using namespace std; bool do_something() { return false; }
int main() { try //outer try { try //inner try { if (do_something() == false ) throw "failure"; }//inner try catch(const char * err) //first handler to cope with the exception { cout <<"first chance to handle " <<<endl; chance" second a given <<" <
WHAT IS THE TYPE OF A CLASS TEMPLATE? A template name by itself is not a type.
size_t n = sizeof(vector); //compile time error; template argument is required Only a template-specialization can be used as a type:
unsigned int n = sizeof(vector ); //fine class myCharVector : public vector //also fine {/*..*/};
Questions or comments about this tip? Contact our tipster, Danny Kalev at dannykk@inter.net.il Danny is a system analyst and software engineer with 10 years of experience, specializing in C++ and object-oriented analysis and design. He is a member of the ANSI C++ standardization committee and a co-author of the Waite Group's C++ How To (Sams Publishing, 1999).
CONSTRUCTORS' CODE INTO A PRIVATE HELPER FUNCTION It is very common to define a class that has more than one constructor. For instance, a string class can define one constructor that takes const char * as an argument, another that takes an argument of type size_t to indicate the initial capacity of the string, and a default constructor. Nonetheless, some identical tasks--such as allocating memory from the free store or assigning the size of the allocated storage to a data member--are done in every constructor. Instead of repeating identical operations in each of the constructors, it is better to move the recurring code into a private helper function and have each constructor call this helper function. For example:
class string { private: void init(); //called by every constructor public: string(const char * s); explicit string(size_t); string(); }; The results are shorter compilation time and easier future maintenance.
HOW TO TERMINATE A PROGRAM SAFELY The standard C functions abort() and exit() perform an immediate program termination, regardless of where they are called from. However, it is not a good idea to terminate a C++ program this way, even when a severe error has occurred. The problem is that none of these functions ensures that destructors of local and static objects are called: abort() calls no destructors at all and exit() invokes only the destructors of objects in the scope where it was called from, but not in other scopes. You can imagine the disaster that may result when objects that open files, allocate dynamic memory, etc., are not properly destroyed. A better choice is to throw an exception and catch it somewhere in the program. For example:
int main() { try { //do everything here } catch(...) { //handle every exception } } C++ guarantees that when an exception is thrown, all destructors of local objects are called before the appropriate catch statement executes. Remember also that exceptions can give the handler a chance to fix the problem and continue, whereas abort() and exit() are unrecoverable.
AN EASY AND EFFICIENT WAY TO INITIALIZE LOCAL AUTOMATIC STRUCTS The easiest, most efficient, and future-proof way to initialize local structs with automatic storage is as follows:
struct PlainData { char name[20]; long ID; char phone[15]; //... other fields as many as you like };
int main() { PlainData data ={0} ;/*initializes the entire struct instance with binary zeroes*/ } This technique is significantly better than manual assignment of values to all the fields of the struct. In addition, the {0} initializer can be applied to every struct type, so even if you make changes in the definition of PlainData, it is guaranteed that its members are all zero-initialized.
HOW TO OVERLOAD POSTFIX AND PREFIX OPERATORS For some objects, the distinction between prefix and postfix overloaded operators is necessary. Postfix operators are declared with a dummy int argument to distinguish them from their prefix counterparts (which take no arguments). For example:
claress Date { //... public: Date& operator++(); //prefix Date& operator--(); //prefix Date& operator++(int unused); //postfix Date& operator--(int unused); //postfix }; You can use the overloaded operators like this:
void f() { Date d, d1; d1 = ++d; /* prefix; first increment d and then assign to d1 */ d1 = d++; /* postfix; first assign, increment afterwards */ }
WHAT'S IN A NAMESPACE? A namespace is a scope of declarations. In the following example, the variable n is declared in two distinct namespaces:
void f() { int n; // 1 } class A { int n; // 2 }; The two declarations refer to different variables because they appear in different namespaces. However, the function f() and the class A are declared in the global namespace. This means that f() and A are visible from other scopes. For example, you can call f() from within the class A. Such global type declarations can be problematic because they might cause name conflicts--the same name can be used to denote different entities, but when these entities are global, the result is name ambiguity. Even if the names are declared in separate source files, the linker will issue an error message on duplicate names. To overcome this problem, namespaces are used. Namespaces enable you to declare entities with identical names in a restricted scope, thereby avoiding name conflicts:
namespace MINE { void f() {/*...*/} } namespace YOURS { void f() /* distinct for f() in namespace MINE */ {/*...*/} } int main() { /* the two functions can be used without interference */ MINE::f(); YOURS::f(); }
ASSIGNMENT OPERATOR SHOULD NOT BE VIRTUAL Unlike ordinary member functions of a base class, the assignment operator is not inherited. The user can redefine it in a derived class. Otherwise, the compiler will automatically synthesize an assignment operator for the derived class. So there is not much point in declaring the assignment operator virtual.
REMEMBER TO UPDATE COMMENTS WHENEVER CODE IS CHANGED Only one thing is worse than a nondocumented source file:
misleading documentation in a source file. For example, a detailed
description of a member function that no longer exists, or a
documentation of what used to be a default constructor that now
appears to be taking three mysterious arguments, can turn software
maintenance into a nightmare. So whenever you change your code or
re-edit a machine-generated source file, do not forget to update the
comments appropriately. --------------------------------------------------------------------------------
USE CONST VARIABLES INSTEAD OF #DEFINE MACROS Using #define to create constants is not recommended. Macros are substituted by the preprocessor, so the compiler usually cannot detect anomalies such as type mismatch (see example). Furthermore, most symbolic debuggers will not allow you to examine a macro's value during debugging. #define OK 1. /* OOPS! a double instead of an int; undetected */ Instead, try using const int: const int OK = 1; In this case, the programmer cannot mistake a float for an int
because the compiler will detect the typo. In addition, the value of
the const variable can be examined during debugging. -------------------------------------------------------------------------------- WHAT IS A DEPRECATED FEATURE? One of the consequences of the evolution and standardization of a programming language is the gradual removal of undesirable, dangerous, or redundant features and constructs. A deprecated feature is one that the current edition of the C++ ANSI/ISO Standard regards as normative, but that is not guaranteed to be part of the Standard in future revisions. By deprecating a feature, the standardization committee expresses the desire to have the feature removed from the language (removing the feature from the language altogether is impractical because existing code will break). By deprecating a feature, the Standardization committee enables the users to remove it gradually from existing code and replace it with the recommended form. Examples of deprecated features are applying postfix ++ to a bool variable, and implicit int declarations:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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 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++ */ 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:
int main() --------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
MINE::C c; /* global object of type MINE::C */ int main() 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:
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:
int main() --------------------------------------------------------------------------------
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 Radio : public ElectricAppliance {/*...*/}; class RadioTape: public Radio, public Tape {/*..*/};
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
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:
--------------------------------------------------------------------------------
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
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
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 D { --------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
D d; --------------------------------------------------------------------------------
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 yourproj_vector /*another type of 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:
--------------------------------------------------------------------------------
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:
void file_copy( const File & source, File & target) --------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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 Derived: public CommonRoot Derived d; /* OK */ --------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
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:
void OS_Register( Mem_Manager& mm); int main() --------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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 derived : public base --------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
NESTING NAMESPACES Namespaces can be nested. Nesting is useful in large-scale projects
in namespace proj /* entire project */ Although two distinct classes called Connection are used in the
same int main()
DESTRUCTORS SHOULD HANDLE EXCEPTIONS LOCALLY As a rule, a destructor should never throw exceptions because it
may void cleanup() throw (int); C::~C() In the example, the exception that might be thrown from cleanup()
is
DECLARE FRIENDS PUBLICLY The access specifier of friend declarations is ignored by the bool operator == (const Date& d1, const Date& d2); class Date
AVOID IMPROPER INHERITANCE Using public inheritance with classes that do not fulfill the is-a Thus, using public inheritance to derive Stack from List is not a
good
AVOIDING A COMMON PITFALL WITH OCTAL NUMERALS A literal integer proceeded by 0 (zero) is an octal numeral. When const int warning = 10; switch (status) In the example, the programmer's intention was to format the
USING NONVIRTUAL MULTIPLE INHERITANCE Virtual inheritance is used to avoid multiple copies of a base
class class Scrollbar Now imagine a window that has both vertical and horizontal
scrollbars. class MultiScroll: public VertScrollbar, To be able to scroll a MultiScroll up and down as well as left and
AUTOMATIC TYPE DEDUCTION OF A ZERO INITIALIZER The literal 0 is an int. However, it can be used as a universal void *p = 0; /* 0 treated as a null pointer */
THE LIFETIME OF A BOUND TEMPORARY OBJECT You can safely bind a reference to a temporary object since the class A int main() In the example, the destruction of the temporary object to which
ref
THE SIZE OF A COMPLETE OBJECT IS NEVER ZERO An empty class doesn't have any data members or member functions. class Empty {}; There is a good reason for this rule. If a complete object were
DEFINING WIDE CHARACTER LITERALS A string literal is a sequence of one or more characters enclosed
in const char txt[] = "ABC"; /* a char literal */ By default, the compiler assumes a char-based literal. To create a const wchar_t wtxt[] = L"ABC"; /* now a wchar_t literal*/ The compiler is case sensitive; therefore, a capital L has to be
used.
EXCEPTIONS THROWN DURING OBJECT CONSTRUCTION AND MEMORY LEAKS Operator new performs two operations: It allocates memory from the No, it doesn't. The allocated memory is returned to the system
before
DO NOT USE OPERATOR NEW IN A THROW STATEMENT Dynamic allocation of an exception (as in the example below) is a
bad class Err void f() There are at least two problems with this approach. First, an if (disaster) It is guaranteed that the temporary object persists until the
PROPERTIES OF STATIC STORAGE Global objects, static data members of a class, namespace variables int num; /*global variable */ int func() class C namespace NS
---------------------------------------------- ABSTRACT DATA TYPES AND ABSTRACT CLASSES The terms "abstract data type" and "abstract class" refer to two In contrast, an abstract class is anything but an abstract data
type.
WHAT "STACK UNWINDING" MEANS When an exception is thrown, the implementation first searches for
an
RELOCATING DECLARATIONS AS AN OPTIMIZATION MEASURE On some occasions, the performance boost that can result from
moving void use() The local object s1 is unconditionally constructed and destroyed in void use() Now the object s1 is constructed only when it is really needed.
When
INLINE ISSUES OF CONCERN Two conundrums are associated with inline functions. The first has
to Another problem can arise with inline functions in third-party code
THE SIZE OF AN ENUM TYPE IS NOT FIXED In C, the size of an enumeration equals the sizeof(int). In C++,
the enum Distance Therefore, do not assume that an enum necessarily occupies the same
THE DEFAULT RETURN VALUE OF MAIN() Consider the following program: int main() The programmer did not write an explicit return statement inside
OVERLOADING THE SUBSCRIPT OPERATOR PROPERLY When you overload operator [], remember to define two versions class MyString The const version of the subscript operator is needed when its
object void f(const MyString& str)
Y2K-COMPLIANT DATE FORMATS Fortunately, the < time.h > functions and data structures are Y2K size_t strftime(char *str, formats a tm struct according to the format specified in fmt and %a /* name of weekday */ Additional symbols exist for the hour, minutes, seconds, and so
forth. %y /* two digit year, without the century */ To avoid any potential Y2K problems, you should always use the %Y
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 int arr[3] = {1, 2, 3}; Now consider an almost identical version of this example: vector < int > vi ( & arr[0], Seemingly, both forms are interchangeable--well, not exactly. While The first example is perfectly legal, while the second one is
STANDARD SPEAKING: WHAT IS A SIDE EFFECT? A "side effect" is a change in the state of the execution
environment.
THE PERILS OF MACROS Even macros that look harmless can have detrimental effects. For #define twice(x) ((x)+(x)) The macro twice is well parenthesized and performs a simple
addition int n = 1; Since ++n equals 2, one might expect (rather naively) that sum
would inline int twice(int x) { return x+x; } the result would have been 4, as expected.
USE THE CONDITIONAL OPERATOR SPARINGLY The conditional operator, ?, is shorthand for an if-else sequence
of int n = 1; What is the value of n after the second statement executes? One
can't n = (n != 0) ? (n+1) : 0; /* well-behaved, still cryptic*/ is still unreadable. Now consider a much simpler and well-behaved
form if (n != 0) Thus, using the conditional operator to save a few keystrokes is a
bad
TIME REPRESENTATION C and C++ represent time as a positive integer that contains the
GUARANTEES ABOUT THE SIZES OF INTEGRAL TYPES The integral types char, short, int, and long have
GUARANTEES ABOUT THE SIZES OF INTEGRAL TYPES The integral types char, short, int, and long have
A VIRTUAL MEMBER FUNCTION CAN BECOME PURE VIRTUAL IN A DERIVED
CLASS In general, a derived class that inherits a pure virtual member class Base class Derived : public Base You wouldn't normally do that. Sometimes, however, this is the only
INTEGRAL TYPES WITH PORTABLE SIZES The actual size of built-in integral types such as char, short,
int, int8 //signed 8 bits These integral types are defined in the standard header < cstddef > uint8 //unsigned 8 bits
MINIMIZE THE USE OF DYNAMIC MEMORY ALLOCATION Pointer-based operations are less frequently needed than they may class PointerMisuse PointerMisuse::PointerMisuse() PointerMisuse::~PointerMisuse() Even experienced programmers are often inclined to use this class ProperUse ProperUse::ProperUse() Not only is this version safer (remember that dynamic memory
GET A FREE C++ COMPILER FOR WINDOWS Dev-C++ is a free graphical C++ compiler for Windows 95, 98, and
NT. http://www.bloodshed.nu/devc.html
THE MEMORY LAYOUT OF IDENTICAL STRING LITERALS Whether identical string literals are treated as distinct objects extern const char msg1[] = "hello"; Some implementations might store the constants msg1 and msg2 at the
UNDERSTANDING MEMORY ALIGNMENT Most CPUs require that objects and variables reside at particular struct Employee Apparently, Employee should occupy 11 bytes (4+3+4). However, most
CONSTRUCTING OBJECTS ON PREALLOCATED CHAR ARRAYS The memory buffer returned by operator new is guaranteed to have
the #include < new > class Employee{ /*...*/}; void f()
DELETING MULTIDIMENSIONAL ARRAYS You can allocate a multidimensional array dynamically, as in the int*pp[10]; /* array of ten pointers */ Remember that you have to use a loop to delete a multidimensional for (int k=0; k < 10; k++) Never use the following forms to delete a multidimensional array: delete pp; /* undefined behavior */ None of them ensures that the elements of the multidimensional
array
USE STATIC ALLOCATION FOR BUFFERS WITH A FIXED SIZE Imagine that you have to write a stock quote application that
accepts class QuoteString
OVERLOADING THE FUNCTION CALL OPERATOR Overloading the function call operator can be confusing because the class A void A::operator ()(bool debug) const //definition
THE ROLE OF IMPLICITLY DECLARED CONSTRUCTORS If a class has no user-declared constructor, and it doesn't have
const class C void f() The programmer did not declare a constructor in class C. Therefore,
THE C_STR() AND DATA() MEMBER FUNCTIONS OF STD::STRING Class std::string provides two member functions that return the
const void f() The member function string::data() also returns a const char *
GUIDELINES FOR WRITING PORTABLE CODE Contrary to what most people believe, portability doesn't guarantee
COPY CTOR AND ASSIGNMENT OPERATOR Although the copy constructor and assignment operator perform
similar string first "hi"; On the other hand, the assignment operator is invoked when an
already string second; Don't let the syntax mislead you. In the following example, the
copy Date Y2Ktest ("01/01/2000");
EVALUATION ORDER OF A MEMBER-INITIALIZATION LIST Whenever you use a member-initialization list, the compiler
transforms class A Since a is declared before b, the member-initialization list is A(int aa, int bb) : a(aa), b(bb) {}/* transformed by the compiler
to While the transformation of initializers is harmless in this case, 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
USING A TEMPLATE AS A TEMPLATE'S ARGUMENT You can use a template as a template's argument. In the following vector < vector < unsigned char > > vmessages; Note that the space between the left two angle brackets is
mandatory. typedef vector < unsigned char > msg;
ALTERNATIVE REPRESENTATIONS OF OPERATORS C++ defines textual representations for logical operators.
Platforms and &&
BEFORE PROFILING YOUR APPS... If you intend to optimize your software's performance, be sure to To conclude, debugging and optimization are two distinct
operations.
UNDERSTANDING REFERENCE COUNTING A reference counting class counts how many object instances have an
TEMPLATES AND INHERITANCE A common mistake is to assume that a vector < Derived > is like a class Base {/**/}; However, there is no relationship between classes generated from
the
TEMPLATES AND INHERITANCE A common mistake is to assume that a vector < Derived > is like a class Base {/**/}; However, there is no relationship between classes generated from
the
WHEN TO USE A VOID-CAST Some rigorous compilers issue a warning message if you ignore a int func(); Indeed, ignoring a function's return value may indicate a bug. int main()
BEGIN() AND END() MEMBER FUNCTIONS OF STL CONTAINERS All STL containers provide the begin() and end() pair of member vector < int > v(1); However, end() returns an iterator pointing one position past the
last vector < int > ::iterator p = v.end()-1; /*p points to v's last
CONTAINER'S REALLOCATION AND ITS CONSEQUENCES When a container has exhausted its free storage and additional Clearly, frequent reallocations can impose a significant
performance vector < int > vi(1000); /* make room for at least 1000 elements */ You can also specify the size of the vector object after
construction. vi.reserve(2000); /* make room for at least 2000 elements */
CONTAINER'S REALLOCATION AND ITERATOR INVALIDATION When a container reallocates its elements, their addresses change #include < iostream > int main() In this example, it may well be the case that payroll reallocated for (int i = 0 ; i < 10; i++)
DISPLAYING THE LITERAL REPRESENTATION OF BOOL VARIABLES By default, iostream objects display bool variables as 0 and 1. You #include < iostream >
THE COPY ALGORITHM The Standard Library provides a generic function, copy(), which you #include < algorithm > /* definition of copy */ int main()
---------------------------------------------- INTERACTING WITH THE OPERATING SYSTEM In general, API functions and classes enable you to interact with
the #include < cstdlib >
---------------------------------------------- AVOID MISUSES OF EXCEPTION HANDLING Although you can use exceptions instead of for-loops and break #include < iostream > This code is inefficient because of the performance overhead of As a rule, use exceptions to report and handle run-time errors
OVERLOADING OPERATORS FOR ENUM TYPES For some enum types, it may be useful to overload operators such as
++ enum Days {Mon, Tue, Wed, Thur, Fri, Sat, Sun}; Days & operator++(Days & d, int) /* int indicates postfix ++ */ int main()
---------------------------------------------- FORWARD-DECLARING I/O CLASSES AND TEMPLATES The standard header < iosfwd > contains forward declarations of the #include < iosfwd > In the example, the declaration of the friend function does not
need a
OVERLOADING NEW AND DELETE FOR A CLASS It is possible to override the operators new and delete and define
a C* p = new C; invoke the class's versions of new and delete, respectively.
Defining In the following example, class C overloads operator new to alter
the #include < cstdlib > /* malloc() and free() */ void* C::operator new (size_t size) throw (const char *) void C::operator delete (void *p) Note that overloaded new and delete are implicitly declared static.
SOME QUIET DIFFERENCES BETWEEN C AND C++ There are a few semantic differences between C and C++ in the In C, the size of an enum type is the same as the size of an int.
In In C, the result of applying operator sizeof to a character
constant,
STRING LITERALS AND TEMPLATE ARGUMENTS A template can take the address of an object with external linkage
as template < class T, const char * > class A
WHAT ARE LVALUES AND RVALUES? An object is a contiguous region of memory storage. An lvalue #include < string > An lvalue can appear in a context that requires an rvalue; in this
THE "BIG THREE RULE" VERSUS THE "BIG TWO RULE" The famous "Big Three Rule" says that if a class needs any of the
Big class Year Class Year does not allocate memory from the free store, nor does
it
TWO FORMS OF DYNAMIC_CAST The operator dynamic_cast comes in two flavors. One uses pointers
and void f(Shape & shape) In this example, dynamic_cast examines the dynamic type of Shape by In contrast, the next example uses a reference dynamic_cast. You #include < typeinfo > /* for std::bad_cast */
DEFINING A STATIC MEMBER FUNCTION A static member function in a class can access only other static class Clock int main() Static members are used when all other data members of an object
are
CALLING A MEMBER FUNCTION FROM A CONSTRUCTOR You can safely call member functions--virtual and nonvirtual
alike--of class A B's constructor calls f() and g(). The calls are resolved to B::f()
BOOSTING PERFORMANCE OF LEGACY SOFTWARE When you port pure C code to a C++ compiler, you may discover
slight Why is this? To support these features, a C++ compiler appends
VIRTUAL FUNCTIONS SHOULD NOT BE DECLARED PRIVATE It is customary to extend virtual functions in a derived class by
NAMESPACES DO NOT IMPOSE ANY OVERHEAD The use of namespaces in your application does not incur runtime or
EFFICIENCY OF STRING COMPARISONS Class std::string defines three versions of the overloaded == bool operator == (const string & l, const string & r); This proliferation may seem redundant, since std::string has a The C++ Standardization Committee's intent was to make comparison
DYNAMIC_CAST AND ACCESS SPECIFICATIONS Operator dynamic_cast fails when it cannot convert its argument to
the class Container {/*..*/ }; int main() In the example above, dynamic_cast fails because Stack is not
publicly
PREFER OBJECT INITIALIZATION TO ASSIGNMENT In general, initializing an object is more efficient than
assignment. string s1 = "Hello "; The compiler transforms the statement both = s1+s2; into this: string temp (s1 + s2); /* store result in a temporary */ Using initialization rather than assignment is more efficient: string both = s1+s2; /* initialization */ This time, the compiler transforms the expression both = s1+s2;
into string both (s1 + s2); In other words, the construction and destruction of a temporary are To conclude, object assignment requires the creation and
destruction
FORWARD DECLARATIONS AND TYPEDEF NAMES It is illegal to use forward declarations with typedef names: class string; /* illegal, string is a typedef name */ Even a typename won't do here: typename std::string; /* still illegal */ Can you see what the problem is with these forward declarations? /* note: this is a simplified form */ These forward declarations don't provide the necessary information
THE ROLE OF ALLOCATORS Every STL container uses an allocator. Allocators encapsulate the
REPORTING AN ERROR DURING OBJECT CONSTRUCTION Constructors have no return value (not even void). Therefore, you
have The first technique uses C-style error handling: When an error
occurs int errcode = OK; /* global flag */ Date's constructor turns on the global flag errcode to indicate a
USING PARAMETERS TO REPORT AN ERROR DURING OBJECT'S CONSTRUCTION Using global variables to indicate runtime errors has many Date::Date(const char *datestr, int & status)
THROWING EXCEPTIONS TO REPORT AN ERROR DURING OBJECT CONSTRUCTION Many implementations still don't support exception handling very
well. class X { Date::Date(const char *datestr) throw (X) int main()
FUNCTION ARGUMENTS EVALUATION ORDER The order of evaluation of function arguments is unspecified. This bool x; /* global */ Suppose also that g() changes the value of x. One cannot tell which
THE NEW LONG LONG DATA TYPE The long long type denotes an integral data type that is at least
as unsigned long long distance_to_star; The suffix ll or LL can be added to a literal integer to indicate a const long long year_light_in_km = 9460800000000LL; Likewise, the suffixes ull and ULL can be added to a literal
integer
STATIC CLASS MEMBERS MAY NOT BE INITIALIZED IN A CONSTRUCTOR You cannot initialize static data members inside the constructor or
a class File Although compilers flag such ill-formed initializations as errors, class File File::locked = false; /* OK */ Alternatively, for const static members of an integral type, the class Screen
ALTERNATIVE STL IMPLEMENTATION The STLport organization offers an alternative implementation of
the http://www.stlport.org/index.shtml The site contains installation directions and extensive
documentation
RETURNING A VOID EXPRESSION FROM A VOID FUNCTION Examine the following code snippet. Does your compiler accept it? void a_func_returning_void(); At present, most compilers will not accept this code, although it's
CATCH EXCEPTIONS BY REFERENCE Although you can catch an exception by value, as in catch( Exception except) /* by value */ it's better to catch an exception by reference: catch( Exception & except) /* by reference */ Catching an exception by reference has several advantages. First,
pass
THE CC++ PARALLEL PROGRAMMING LANGUAGE CC++ (this is not a typo) is a parallel programming language based
on http://globus.isi.edu/ccpp/ Although CC++ is not standardized yet, it's interesting to examine
the
DATA AGGREGATES The term "aggregate" refers to an array or a class with no Aggregates differ from class objects and arrays of class objects in struct Employee /* using the '={0}' initializer to initialize aggregates */ Note that nonaggregate objects and arrays cannot be initialized
this
THE C++ EXCEPTION HANDLING MODEL In a resumptive model, after an exception has been handled, the
GET A FREE COPY OF THE GNU C/C++ COMPILER 2.95 The GNU project focuses on the development and distribution of open http://www.gnu.org/software/gcc/gcc-2.95/gcc-2.95.2.html
WHY THE COPY CONSTRUCTOR'S PARAMETER MUST BE PASSED BY REFERENCE The canonical form of a copy constructor of a class called C is C::C(const C & rhs); You can also declare the copy constructor's parameter as follows: C::C(C & rhs); /*non-const, OK*/ That is, the const specifier is not mandatory. Note, however, that
the C::C(const C rhs); /* Error */ Can you see why the parameter must be passed by reference? Whenever
A COMMON MISTAKE IN TEMPLATE INSTANTIATION The type that a template takes as an argument must have external int main() The line numbered 1 causes a compilation error because it attempts
to struct S int main()
---------------------------------------------- A FUNCTION TEMPLATE CANNOT BE VIRTUAL A reader tried to declare a template as a virtual member function
of a template < class T > struct A It's not a compiler bug. The C++ Standard says (14.5.2 p 3): "A
member
ADD A CATCH(...) BLOCK TO YOUR EXCEPTION HANDLERS HIERARCHY If your application uses exception handling, it is recommended that int main() Note that a catch(...) block cannot detect the type of the
exception
APPLYING OPERATOR TYPEID TO NONPOLYMORPHIC OBJECTS Operator typeid retrieves the runtime type information of a #include < typeinfo > void f() Adding a virtual member function to B will force the typeid
operator cout << typeid(*p).name(); Note that applying dynamic_cast to fundamental types or
nonpolymorphic
---------------------------------------------- AUTO_PTR SHOULD NOT HOLD ARRAYS OF OBJECTS The auto_ptr class template automatically destroys an object that
is #include < memory > /* for auto_ptr */ Note, however, that binding auto_ptr to an array of objects results
in auto_ptr < string > ps (new string[2]); /* undefined behavior */
---------------------------------------------- AVOID TYPEDEF'S IN STRUCTS DEFINITIONS In olden days, before C++ appeared, it was customary to declare a typedef struct DATE_TAG This way, one could create an instance of the struct without having
to /* C code */ In C++, the use of a typedef in this context is unnecessary because // C++ code Therefore, avoid declaring structs as typedef names in C++ code.
AVOIDING RECURRENT POINTER DELETION The result of applying delete to the pointer more than once is std::string ps = new std::string; Of course, the solution to this bug is to avoid deleting the same std::string ps = new std::string;
---------------------------------------------- BEGINNING C++ One of the most frequently asked questions I receive is, "I've done A good primer book and a compiler can provide you with all the
DEFAULT PARAMETER VALUES ARE NOT PART OF A FUNCTION'S TYPE Although the default parameter values of a function must appear in
its void f(int n = 6); The attempt to overload f() is illegal because the compiler
considers
---------------------------------------------- DEFINING A FUNCTION IN AN UNNAMED NAMESPACE The use of the keyword "static" to limit the linkage of classes and namespace void func() /* most likely an error */ The programmer's intention was to define the function that was namespace
---------------------------------------------- DYNAMIC BINDING AND STATIC BINDING Not every call to a virtual function is resolved dynamically. For class A Dynamic binding applies in two cases: when calling a virtual
function void f(A & ref, A* pa) You can bypass the dynamic binding mechanism by using the member pa->A::func(); /* always resolved statically */
---------------------------------------------- FORWARD-DECLARING A NAMESPACE MEMBER Suppose you need to forward-declare class Interest, which is a
member class Bank::Interest; Alas, the qualified name Bank::Interest will cause a compilation
error namespace Bank The example above reopens namespace Bank (which is defined in
another
FUNCTION SIGNATURES A function's signature provides the information needed to perform
the
GETTING ALONG WITHOUT NULL REFERENCES In C, algorithms such as bsearch() and lfind() return a null
pointer int & locate(int * pi, int size, int value) The return statement fakes a null reference by binding a reference
to You can resolve the lack of null references with several
approaches. int & locate(int * pi, int size, int value)
---------------------------------------------- HELPING THE COMPILER DEDUCE THE TYPE OF A TEMPLATE ARGUMENT In general, the compiler deduces the type of a function template int n = max (5,10); /* max< int > deduced because 5 and 10 are
int's However, sometimes the compiler needs more explicit clues regarding template < class T >T f() The program fails to compile because the compiler cannot deduce
what int i = f < int > (); /*now OK */
---------------------------------------------- INITIALIZE CLASS'S DATA MEMBERS EASILY Suppose you want to initialize all the data members of class Book class Book One way to do that is by assigning a value to every member inside
the Book::Book() memset() also initializes the object's virtual table pointer. struct BookData Next, derive Book from that struct using private inheritance: class Book : private BookData {...}; And that's all. Now every data member of Book is automatically
INITIALIZING ARRAYS IN A CLASS You cannot initialize an array class member by a
member-initialization class A The following forms won't compile either: A::A() : buff('\0') {} /* ill-formed */ Instead, you should initialize arrays inside the constructor body: A::A()
INLINE OR __FORCEINLINE The decision whether a function declared inline is actually What are the uses of the nonstandard keywords? The nonstandard
keyword
MAKING A CLASS TEMPLATE A FRIEND OF ANOTHER CLASS TEMPLATE A class template can be a friend of another class template. For template < class U > class D{/*...*/}; In this example, every specialization (instantiation) of the class
---------------------------------------------- OPEN MODE FLAGS In older stages of C++, the ios::nocreate and ios::noreplace open Along with the templatization of < fstream >, the C++
standardization
---------------------------------------------- OPTIMIZATIONS THAT ARE CARRIED OUT BY THE LINKER While most of the code optimizations are performed at compile time, With some linkers, you can control the number of passes the linker
---------------------------------------------- OPTIMIZING LOOPS Consider the following code: class Foo{/*..*/}; for (int j =0; j < MAX; j++) This for loop is very inefficient: It constructs and destroys a Foo something; // avoiding a temporary This improved version constructs and destroys the object only once, In general, you should avoid declaring temporary objects inside
loops.
OVERLOADED SUBSCRIPT OPERATOR SHOULD RETURN A REFERENCE When you overload the operator [], remember that its non-const
version class IntArray IntaArray iar;
---------------------------------------------- OVERLOADING OPERATOR + PROPERLY The built-in operator + takes two operands of the same type and You can either declare operator + as a member function of its class
or class Date The friend version is preferred because it reflects symmetry
between
PACK A LONG PARAMETER LIST IN A STRUCT A function having a long list of parameters, as in void retrieve(const string & title, often becomes a maintenance problem, because its parameter list is struct Item Packing the parameters in a single struct has two advantages.
First,
STRUCTURED EXCEPTION HANDLING VERSUS STANDARD EXCEPTION HANDLING Many C compilers provide a feature called structured exception The types of exceptions raised by SEH and EH, as well as their
---------------------------------------------- THE BOOST WEB SITE The Boost Web site offers assorted free C++ libraries and classes, http://www.boost.org
THE DANGERS OF OBJECT SLICING Passing by value a derived object to a function may cause a problem class Date }; void f(Date b) /* by value */ Object slicing may result in undefined behavior. Therefore, pass
them
THE USEFULNESS OF PTRDIFF_T C and C++ define a special typedef for pointer arithmetic,
ptrdiff_t, #include < stdlib.h > There are two advantages in using ptrdiff_t instead of int. First,
the
THE WELL-FORMEDNESS OF ZERO SIZED ARRAYS In standard C++, declaring arrays with zero elements is ill-formed: int n[0]; // compilation error However, certain compilers do support arrays of zero size as int n = new int[0]; The standard requires that, in this case, new allocate an array
with While zero-sized dynamic arrays may seem like another C++ trivia
that void * allocate_mem(unsigned int size)
---------------------------------------------- USE EXPLICIT ACCESS SPECIFIERS IN A CLASS DECLARATION In many contexts, C++ provides default access specifiers when you struct Employee{ /*..*/}; Relying on the default access specifiers is not recommended,
though. class Manager : public Employee class BoardMemebr : private Manager By using explicit access specifiers, you ensure that the code is
more
USES OF THE OFFSETOF MACRO The standard macro offsetof (defined in < stdlib.h > ) calculates
the #include < stdlib.h > Note that offsetof works with POD (Plain Old Data) structs and
unions.
USING FUNDAMENTAL TYPES AS A TEMPLATE PARAMETER A template can take ordinary types such as int and char as its template < class T, int n > class Array However, the template argument in this case must be a constant or a const int cn = 5;
---------------------------------------------- VISIT SILICON GRAPHIC'S STL PAGES Silicon Graphics hosts a Web site dedicated to Standard Template http://www.sgi.com/Technology/STL/
VISIT THE C/C++ USERS GROUP WEB SITE The C/C++ Users Group (CUG) is an independent organization that
offers http://www.hal9k.com/cug/
---------------------------------------------- WHY YOU SHOULDN'T STORE AUTO_PTR OBJECTS IN STL CONTAINERS You must have heard that you shouldn't use auto_ptr objects as The C++ Standard says that an STL element must be
"copy-constructible" This is not the case with auto_ptr, though: Copying or assigning
one vector < auto_ptr < Foo > > vf; /*a vector of auto_ptr's*/ When temp is initialized, the member vf[0] is changed: Its pointer Several Visual C++ users report that they have never encountered
any So, don't use auto_ptr in STL containers. Either use bare pointers
or http://www.Boost.org
---------------------------------------------- A C++ DECOMPILER--MYTH AND REALITY A frequently asked question in C++ newsgroups is whether a C++ decompiler exists. It doesn't, and probably it never will. Why is this? First, remember that there is no 1:1 relation between a piece of assembly code and a matching C++ statement. For example, for-loops, switch statements, and while blocks are all transformed into the same assembly directives. Thus, an infinite number of C++ programs can produce the exact assembly code. The same is true for other language constructs that are translated into a common assembly construct: pointers, references, and arrays are all transformed into pointers. Furthermore, each compiler produces a different executable for a given source file. Even if you know the exact compiler brand and version that were used for compiling the program, how can you tell which compile-time options were used? And how can a decompiler reconstitute macros, constants, and typedefs, which are substituted with their underlying definitions before the compiler translates the source code into machine code? Finally, debugging information is usually removed from the
executable. Therefore, a hypothetical decompiler wouldn't be able to
reconstitute the original names of variables, classes, arrays,
pointers, functions, and constants. Thus, developing a utility that
decompiles an executable file into its original C++ source code (or
anything that comes near) is unrealistic. -------------------------------------------------------------------------------- A POSSIBLE DEPRECATION OF VECTOR < BOOL > In the early days of STL, C++ creators decided to include a specialized form of the vector container class called vector < bool >. This decision was plausible and seemed useful at that time. However, the experience gained in recent years shows that vector < bool > is problematic in many respects. It also contradicts some of the basic requirements with which STL containers must comply. For example, vector < bool > elements are bits. Yet a bit is not an addressable type--you can't define a pointer or a reference to a bit, nor can you allocate single bits using operator new. For this reason, algorithms and iterators can't cope with this type of vector. Worse yet, on most implementations, vector < bool > is significantly slower than other types of vector. The C++ standardization committee is aware of these difficulties.
Chances are good that future versions of the Standard will deprecate
vector < bool >. In the meantime, it's advisable to avoid using it. If
you need a vector of Boolean values, you can use vector of char
instead. -------------------------------------------------------------------------------- A PROBLEM WITH STATIC OBJECTS' INITIALIZATION Examine this class and its global instance: // file Foo.h // file Foo.cpp // file main.cpp Surprisingly enough, the linker might "optimize away" the global object reg, even if you disable all compiler and linker optimizations. What's going on here? According to the C++ standard, the construction of an object defined in a namespace scope (a global object is considered as such) can be "deferred" indefinitely if no other function or object defined in the same translation unit refers to that object and if the object's constructor has no side effects. In other words, the linker may entirely remove reg's constructor call from the program because no other object or function in Foo.cpp refers to reg and because reg's constructor has no side effects. To force the reg's construction, you must refer to it somewhere in
Foo.cpp. Alternatively, you can add an operation with a side effect to
reg's constructor, such as calling printf() from it. -------------------------------------------------------------------------------- ASSIGNING A CHAR POINTER TO A STRING OBJECT You can assign a pointer to char to a string object:
-------------------------------------------------------------------------------- ASSIGNING A ZERO VALUE TO ALL MEMBERS OF A STRUCT Sometimes you need to clear all the members of a struct after it was used. For large structs, you usually use memset(). However, for small structs that occupy 2, 4, or 8 bytes, calling memset() is rather expensive in terms of performance:
Date d;
This two-step operation can be achieved with a single statement: *( reinterpret_cast < int* > ( & d) ) = 0; -------------------------------------------------------------------------------- AVOID EMPTY PARENTHESES IN OBJECT INSTANTIATION Although the following statements are semantically equivalent:
string s(); /* probably an error */ The programmer mistakenly assumed that this statement creates a local string object, but it doesn't--it's interpreted as a declaration of a function named s, which returns a string object by value and takes no arguments. This is very different from the following: string s; This statement, on the other hand, creates an object named s of
type string, which is what the programmer originally intended. As you
can see, the bad habit of placing a redundant pair of empty
parentheses when using new can be dangerous. Avoid it. -------------------------------------------------------------------------------- BOOL CONVERSIONS A value of type bool can be converted implicitly to a value of type int, with false becoming zero and true becoming one. For example: bool b = false; Likewise, an arithmetic type, enumeration, pointer, or pointer to member can be converted to a value of type bool in the following manner: a zero value, null pointer, or null pointer to member is converted to false; any other value (including a negative value) is converted to true. For example: void (A::*pmf)()= & A::f; /*pointer to a member*/ -------------------------------------------------------------------------------- BORLAND'S C++ 5.5 IS NOW GIVEN AWAY--FREE Inrpise (formerly Borland) is now distributing its C++ command-line compiler, Borland C++ 5.5, for free. You can download the compiler here (registration is required): http://community.borland.com/ -------------------------------------------------------------------------------- CALLING A FUNCTION FROM AN OBJECT'S DESTRUCTOR It's perfectly legal to call a member function, virtual or nonvirtual, from an object's destructor. However, you should avoid calling a member function that accesses data members that have already been destroyed. For example:
-------------------------------------------------------------------------------- CHECKING A DATA ENTRY Applications that accept users' input usually need to validate the data. For example, they have to detect whether the user entered alphabetic data instead of a number. The easiest way to do that is to read a string rather than a
numeric data type. After the user has entered the data, you can
examine that string and check its validity. -------------------------------------------------------------------------------- COMPILER'S WARNING MESSAGES ABOUT IDENTIFIER'S LENGTH Unlike C, C++ does not impose any restrictions on the length of identifiers used in a program. Implementations set their own limits, but these are so high that names of variables, classes, functions, and constants can have thousands of characters. Although you wouldn't normally use more than 15 character names because of name-mangling, the compiler may extend an identifier's name with additional characters. For templates, the mangled names can be very long. In Visual C++, a typical mangled name of the cii iterator std::vector < int >::const_iterator cii; can exceed 255 characters. The mangled name of csi std::vector< std::string >::const_iterator csi; is even larger. Some debuggers can't cope with such long names and
issue warning messages. You can safely ignore these messages because
they are related only to the debugger's inability to display extremely
large symbols. The compiler and the linker have no problem coping with
these large names. -------------------------------------------------------------------------------- CONTROLLING THE LIFETIME OF A LOCAL OBJECT Sometimes, you need to force an object to destroy itself because its destructor has certain side effects, such as releasing a lock or closing a file. However, with a local automatic object, the destructor is invoked only when the current block exits. Before you suggest invoking the destructor explicitly, remember that the destructor will be called for the second time when the block exits with undefined behavior. Here is an example: void func(Modem & modem) At this point, you would probably suggest allocating the object dynamically and deleting it when it's done. However, there is a simpler solution--wrap the object in braces: void func(Modem& modem) -------------------------------------------------------------------------------- CONVERSION OF MEMBER-FUNCTION TO POINTER-TO-MEMBER-FUNCTION Does you compiler accept this code?
void (A::*pm)(int) = & A::f; /* now OK */ -------------------------------------------------------------------------------- CONVERTING A DATA TYPE TO A STREAM OF BYTES Sometimes you need to convert a certain data type to a binary stream of bytes. This is particularly useful in dump utilities and debuggers. To do that, create a union that has two members: the type in question and an array of characters with the same size. For example, to examine the binary representation of a pointer to a member function, the following program assigns the address of the member function f() to the union member p and then it examines the corresponding character array bin_ptr: class A typedef void (A::*pmf)(); /*pointer to member*/ -------------------------------------------------------------------------------- COPYING AND ASSIGNMENT OF CONTAINER OBJECTS STL containers define an appropriate copy constructor and overload the assignment operator, thereby enabling you to copy construct and assign container objects easily:
-------------------------------------------------------------------------------- CORRECTLY DECLARING GLOBAL OBJECTS Although the use of global variables is not recommended in general, sometimes it's unavoidable. When using a global variable or object, never instantiate it in a header file because the header is usually #included in several separate source files. Consequently, the linker will complain on multiple definitions of the same object. Instead, instantiate a global in a single .cpp file. This way, you ensure that it's defined only once, regardless of the number of source files used in the project. All other source and header files in the project that refer to that global object need to declare it extern. Here is an example:
/*declaration only; x is defined in another file*/ struct Counter // File b.cpp int x; /* definition of a global variable */ // File main.cpp #include "a.h" -------------------------------------------------------------------------------- CREATING AN ARRAY OF OBJECTS WITH NO DEFAULT CONSTRUCTOR The following class doesn't have a default constructor:
A arr[10]; /* error; no default constructor for class A*/ However, you can still create arrays of class A using an explicit initialization list: A arr[3] = { A(0,0), A(0,0), A(0,0) }; // ok Note that in the declaration of the array arr, every element must be explicitly initialized. This is tedious, especially if you create a large array. Furthermore, you cannot create an array of A dynamically: A * pa = new A[2]; // error Therefore, when you define a constructor for a class, remember to
define a default constructor as well. -------------------------------------------------------------------------------- DECLARING A TYPEDEF As you may know, typedef names can hide intricate syntactic constructs such as pointers to functions or complex template declarations. However, many novices simply don't know how to declare or interpret a typedef declaration. To write a typedef declaration, simply define a variable of the desired type. Suppose you want to create a typedef name that represents "pointer to int". First, define a pointer to int: int * pi; Then, precede the keyword "typedef" to the previous declaration: typedef int * pi; Now the name pi serves as a synonym for "pointer to int". You can use pi in any context that requires a pointer to int: pi ptr = new int; Similarly, to create a typedef name that is synonymous with "pointer to function that returns int and takes int", you first declare such a pointer: int (*pfi)(int); In the definition above, pfi is a pointer to a function that returns int and takes int. Now, add the keyword "typedef" before the definition: typedef int (*pfi)(int); And pfi becomes a typedef name for "pointer to function that returns int and takes int". For example:
-------------------------------------------------------------------------------- DECLARING POINTERS TO DATA MEMBERS Although the syntax of pointers to members may seem a bit confusing at first, it's consistent and resembles the form of ordinary pointers, with the addition of the class name followed by the operator :: before the asterisk. For example, if an ordinary pointer to int looks like this: int * pi; you define a pointer to an int member of class A like this: int A::*pmi; /* pmi is a pointer to an int member of class A */ You initialize a pointer to a member like this:
-------------------------------------------------------------------------------- DECLARING POINTERS TO MEMBER FUNCTIONS Pointers to member functions consist of the member function's return type, the class name followed by ::, the pointer's name, and the function's parameter list. For example, a pointer to a member function of class A that returns an int and takes no arguments is defined like this (note that both pairs of parentheses are mandatory):
int (A::*pmf) ();
Note that you can't take the address of a class's constructor(s)
and destructor. -------------------------------------------------------------------------------- DEFAULT INITIALIZATION A POD (plain old data) type is a union, struct, or class with no user-declared constructors, no private or protected nonstatic data members, no base classes, and no virtual functions. To default-initialize an object of type T means one of the following: * If T is a non-POD class type, the default constructor for T is
called. An object whose initializer is an empty set of parentheses is default-initialized. For example:
Date d = {0}; /* zero initialization */ -------------------------------------------------------------------------------- DELETING CONST POINTERS In earlier stages of C++, deleting a const object, even if that object was allocated dynamically, was impossible. However, the C++ Standard was recently changed and now you can use operator delete to destroy such objects. For example:
-------------------------------------------------------------------------------- DENNIS RITCHIE'S HOME PAGE Dennis Ritchie was the creator of C and a co-author of the legendary book "The C Programming Language." His Web site is probably the best virtual museum of the C programming language. The site includes interesting historical documents, pictures, and memos from the early days the C programming language. Amazingly enough, the original source files of the primeval C compiler (from 1972 or so) are available online! You can find it at http://cm.bell-labs.com/cm/cs/who/dmr/ -------------------------------------------------------------------------------- DETECTING AN UNCAUGHT EXCEPTION An exception is considered caught when its handler has started or when the standard function unexpected() has been invoked, if no matching handler was found. To check whether an uncaught exception is being processed, use the standard function uncaught_exception() (declared in the standard header < exception > ). For example:
-------------------------------------------------------------------------------- DIFFERENCES BETWEEN FLOAT AND DOUBLE In most cases, double is the default floating-point data type you
should use. Variables of type double offer higher precision and
minimize truncation and deviation to a minimum. This is why most of
the functions and algorithms in the Standard Library take and return
double. In general, float should be used only to save memory or when
the use of double imposes additional processing overhead on the CPU. -------------------------------------------------------------------------------- DIFFERENCES BETWEEN VECTOR'S RESERVE() AND RESIZE() MEMBER FUNCTIONS Although the reserve() and resize() member functions of std::vector perform similar operations, they differ in two respects: Unlike resize(), which allocates memory in the requested size and default-initializes it, reserve() allocates memory in the requested without initializing it. In addition, reserve() does not change the size (that is, the number of elements) of a vector; it changes only the vector's capacity. Note that both these functions take the number of elements, not bytes, as their argument. For example:
-------------------------------------------------------------------------------- DO NOT RETURN CONTAINER OBJECTS BY VALUE The overhead of returning an ordinary object by value may be acceptable in most cases. However, returning a container object by value is much more expensive in terms of performance. For example:
-------------------------------------------------------------------------------- DON'T CHANGE THE ACCESS OF A VIRTUAL MEMBER FUNCTION IN A DERIVED CLASS
Although it's perfectly legal to change the access specifier of a virtual member function in a derived class, as in
class Base { public: virtual void Say(); }; class Derived : public Base { private: /* access specifier changed */ void Say(); };
you should never do that. When you use a pointer or a reference to a base class, the object bound to that pointer or reference will behave unpredictably regarding the access specification of its members:
Derived d; Base *p = & d; p->Say(); /* surprise */
Because the binding of a virtual member function is postponed to runtime, the compiler cannot detect that a nonpublic member function is called; although p points to an object of type Derived, in which Say() is a private member function, the compiler assumes that p points to an object of type Base, of which Say() is a public member. Therefore, the code compiles fine when, seemingly, it shouldn't.
----------------------------------------------
DON'T CONFUSE DELETE WITH DELETE[] There's a common myth among programmers that it's okay to use delete instead of delete[] to release arrays of built-in types. For example,
-------------------------------------------------------------------------------- ELIMINATING TEMPORARY OBJECTS C++ creates temporary objects "behind your back" in several contexts. The overhead of a temporary can be significant because both its constructor and destructor are invoked. You can prevent the creation of a temporary object in most cases, though. In the following example, a temporary is created: Complex x, y, z; The expression y+z; results in a temporary object of type Complex that stores the result of the addition. The temporary is then assigned to x and destroyed subsequently. The generation of the temporary object can be avoided in two ways: Complex y,z; In this example, the result of adding x and z is constructed directly into the object x, thereby eliminating the intermediary temporary. Alternatively, you can use += instead of + to get the same effect: /* instead of x = y+z; */ Although the form that uses += is less elegant, it costs only two
member function calls: assignment operator and operator +=. In
contrast, the use of + results in three member function calls: a
constructor call for the temporary, a copy constructor for x, and a
destructor call for the temporary. -------------------------------------------------------------------------------- FALSE MEMORY LEAKS Global objects and objects declared in a namespace scope are
constructed before main() starts. The implementation usually runs
special startup code that constructs such objects (including cin and
cout) and performs additional initializations before main() starts.
Because the memory that these objects allocate is not released before
main() exits, some utilities and debugging tools mistakenly report
that these objects leak memory. There's no need to worry, though. The
memory allocated by cin, cout, and similar objects doesn't really
leak; it's released when these objects are destructed, an operation
that takes place after main() exits. -------------------------------------------------------------------------------- FUNCTION PARAMETERS CANNOT BE USED AS DEFAULT ARGUMENTS The order of evaluation of function arguments is unspecified. Therefore, parameters of a function may not be used in default argument expressions. The following declaration is ill formed because it uses a parameter as a default argument: int f(int n, int m = n); /* error */ -------------------------------------------------------------------------------- HANDLING STRING EXCEPTIONS Several member functions of std::string might throw exceptions. For example, if you use a very large number to specify the initial storage size, string's constructor might throw an exception: string s(string::npos, 'c'); /* throws std::length_error */ In practice, values that are much smaller than the constant
string::npos might also cause a std::length_error exception, depending
on the program's available heap size. For this reason, it's advisable
to check the value you pass to the constructor in advance.
Alternatively, you can use try and catch statements to handle the
exception. -------------------------------------------------------------------------------- HEAP AND FREE-STORE The terms "heap" and "free-store" are used interchangeably when referring to dynamically allocated objects. Are there any differences between the two? Technically, free-store is an abstract term referring to unused memory that is available for dynamic allocations. Heap is the data model used by virtually all C++ compilers to implement the free-store. In practice, however, the distinction between heap and free-store is roughly equivalent to the differences between the memory models of C and C++, respectively. In C, you allocate memory dynamically using malloc(), and release the allocated memory using free(). The allocated memory is not initialized. Dynamic allocations in C are said to take place on the heap. In C++, you perform dynamic allocations with new and release the allocated memory with delete. Operator new automatically initializes the allocated memory (by calling the object's constructor), and delete automatically destroys the object before releasing its memory. In C++, dynamic allocations are said to take place on the free-store. Note that the heap and free-store may reside on distinct physical
memory regions and they might be controlled by different underlying
memory managers. -------------------------------------------------------------------------------- INITIALIZING A BIT STRUCT To initialize a struct that contains bit fields, simply use the ={0} partial initialization list:
-------------------------------------------------------------------------------- INITIALIZING A VECTOR WITH THE CONTENTS OF AN ARRAY Built-in arrays meet the criteria of an STL sequence. Therefore, you can directly initialize a vector with a built-in array:
} -------------------------------------------------------------------------------- INTERNET C++ VIRTUAL MACHINE Implementing a Java-style virtual machine for C++ is a trivial task. The problem is that such a virtual machine would suffer from the well-known ailments of Java: bloated and inherently slow code. At present, at least three independent projects are attempting to define a uniform binary interface for C++ without compromising performance or code size. One of these projects will enable you to link code that was compiled by different compilers into a single executable file. A more interesting project is targeted at the Internet. The Internet C++ Virtual Machine (ICVM) project defines a universal 32-bit virtual machine for C++ that will enable you to run C++ code on every target machine, regardless of the underlying OS and hardware architecture. ICVM promises both high performance and portability. You can read more about ICVM here: http://sr9.xoom.com/icvmcpp/ -------------------------------------------------------------------------------- INTRODUCING POINTERS TO MEMBERS A class can have two general categories of members: function members and data members. Likewise, there are two categories of pointers to members: pointers to member functions, and pointers to data members. The latter are less common, because in general, classes do not have public data members. However, when using legacy C code that contains structs or classes that happen to have public data members, pointers to data members are useful. Pointers to members are one of the most intricate syntactic
constructs in C++, and yet, they are a very powerful feature, too.
They enable you to invoke a member function of an object without
having to know the name of that function. This is very handy
implementing callbacks. Similarly, you can use a pointer to a data
member to examine and alter the value of a data member without knowing
its name. In future tips, I will discuss pointers to members in
further detail and show you how to define and use them. -------------------------------------------------------------------------------- ITERATORS AND REAL POINTERS Suppose you define the following list object and you need the address of one of its elements:
-------------------------------------------------------------------------------- LOCAL OBJECTS' DESTRUCTION ORDER On exit from a scope, destructors are called for all local automatic objects that are declared in that scope, in the reverse order of their declaration. For example:
-------------------------------------------------------------------------------- MEMORY ALLOCATIONS IN SINGLE-THREADED APPLICATIONS On most platforms, the operators new and delete are implemented in
a thread-safe manner so that they can be used in multithreaded
applications. The thread-safe implementation of these operators
imposes an unnecessary runtime overhead in single-threaded
applications. Therefore, in time-critical single-threaded
applications, you may consider overloading new and delete for
particular classes or using a custom memory pool. -------------------------------------------------------------------------------- MINIMIZING CODE BLOAT ASSOCIATED WITH TEMPLATES Incautious usage of templates can cause a problem known as code bloat: The compiler reduplicates the template's body for each distinct specialization (template instance), and as a result, the program's size increases dramatically. In the following example, three distinct copies of the template min() will be generated--one for each type used:
This technique can be applied to pointers, too: First, cast them to
void * and then cast back to their original type. Notice that for very
small templates like min(), this technique isn't worth the bother.
However, when using larger templates, you might want to consider its
use. -------------------------------------------------------------------------------- MORE ON OPERATOR OVERLOADING Here are some pointers on operator overloading. First, you cannot invent a new operator during overloading:
-------------------------------------------------------------------------------- NAMESPACE ALIASES
Choosing a short namespace name might eventually lead to name clashes. On the other hand, very long namespaces tend to be laborious. For this purpose, you can use a namespace alias. A namespace alias is a synonym for another namespace. For example:
namespace spring_project { class Date{ /*..*/ }; const int MAX_NUM = 512; /* .. more stuff */ }
int main() { namespace sp = spring_project; /* sp is a namespace alias */ sp::Date d; /* equivalent to spring_project::Date but shorter */ }
----------------------------------------------
OPERATORS CANNOT BE OVERLOADED FOR USER-DEFINED TYPES An overloaded operator must take at least one argument of a user-defined type (operators new and delete are the only exception). This rule ensures that users cannot alter the meaning of expressions that contain only built-in types. For example:
-------------------------------------------------------------------------------- PERFORMING CASE-INSENSITIVE COMPARISON OF STRINGS The overloaded operator == performs a case-sensitive comparison of string objects. If you check the following strings for equality using ==, the result will be false: string s1 = "Jellylorum"; For most applications, this is the expected behavior. However, sometimes it's necessary to perform case-insensitive comparisons. Unfortunately, there is no standard version of operator == that performs a case-insensitive comparison. There are several workarounds, most of which require familiarity with the underlying machinery of STL, the notion of char traits, function objects, binders, and so on. For many programmers who simply wish to perform case-insensitive comparisons, these solutions are too complicated. The simplest way to get around this is by overloading the standard C function strcimp(). The new overloaded version takes strings rather than char *:
return stricmp (s1.c_str(), s2.c_str()) == 0; Now you can use the overloaded function: bool b = stricmp(s1,s2); -------------------------------------------------------------------------------- PLACE INCLUDE GUARDS IN YOUR HEADER FILES Including a file more than once during the same compilation session is likely to cause errors due to multiple declarations. To avoid this, place an include guard in every header file. For example:
/* declarations come here */ #endif /* appears at the end of the file */ -------------------------------------------------------------------------------- PREFER STL CONTAINERS TO HOMEMADE ONES Many programmers still use homemade (or worse yet--handmade)
container classes such as list and vector instead of using the
Standard Template Library (STL). There are significant advantages in
preferring STL containers to any other container framework: STL
containers are portable, their performance is usually superior, and
they have already been debugged and tested for you. -------------------------------------------------------------------------------- PREFIXES OF HEXADECIMAL AND OCTAL LITERALS Hexadecimal and octal literals are indicated by special prefixes: 0x and 0, respectively. For example: int n =0x1000; // hex -------------------------------------------------------------------------------- PREVENTING MEMORY LEAKS A memory leak occurs when a program fails for some reason to release memory that it allocated on the heap. The program won't reclaim such leaked memory because both C and C++ assume that if you don't explicitly free dynamically allocated memory, you want it to remain available throughout the lifetime of the program. Some operating systems reclaim leaked memory after the process has terminated. On other operating systems (DOS, for example), leaked memory is lost until the system is rebooted. In either case, recurrent memory leaks cause an application to bog down or crash altogether. The easiest way to avoid memory leaks is by not using dynamic
allocations in the first place. If possible, use automatic and static
memory instead. When dynamic memory allocation is unavoidable,
remember to add a matching delete call for every object allocated by
new. -------------------------------------------------------------------------------- REFERENCE COUNTING ON STRING C++ allows an implementation to use reference counting on std::string. However, this is not a requirement. A reference-counted implementation must have the same semantics as a nonreference-counted one. For example:
-------------------------------------------------------------------------------- ROUNDING NUMBERS The standard < math.h > header declares the function ceil() and
floor() that round the fractional part of a floating point number.
ceil() returns the smallest integer that is no less than its argument.
For example, ceil(5.4) returns 6. By contrast, floor() returns the
largest integer that isn't greater than its argument. For example,
floor(5.4) returns 5. -------------------------------------------------------------------------------- SAFE USE OF INLINE Excessive use of inline functions might bloat the size of the executable and reduce execution speed. For this reason, many programmers avoid function inlining altogether because they can't predict how inlining will affect performance. However, very short functions (for example, a function that merely calls another function or a function that returns a data member of its object) are always good candidates for inlining. For example:
-------------------------------------------------------------------------------- SEARCHING FOR PATTERNS OF TEXT IN WORDPERFECT WordPerfect's comprehensive Find feature allows you to search for text in a number of ways. In addition to searching for the standard exact matches, you can specify a broader search by using wild cards in your searches. A wild card represents an unknown character in a search string. WordPerfect has two wild-card options: one unknown character and many unknown characters. If you need to find all instances of the company name XYZ Company to confirm that you've referenced it consistently throughout your document (as Company rather than Co. or Corp. or Corporation), you could use a many-character wild card in the search string. Press Ctrl-F to open the Find feature and type XYZ Co in the Find text box. Choose Match, Codes from the menu, select *(Many Char) from the codes list, and click Insert & Close. Your search string will be XYZ Co[*(Many Char)] To start the search, click Find Next. WordPerfect will find all instances of phrases starting with XYZ Co. A single unknown character search limits the variable character to one letter. For example, to find all instances of a three-letter word beginning with A and ending with C (AAC, ABC, ACC, and so on), you would type A in the Find box, choose Match, Codes from the menu, select ?(One Char) from the code list, click Insert & Close, and type C at the end of the search string, so it looks like this: A[?(One Char)]C -------------------------------------------------------------------------------- SOME DESIGN AND CODING RULE OF THUMBS Writing code from scratch, without any prior design or plan, is bad
programming practice. However, the opposite--over-engineering--can be
just as harmful. Over-engineering is the use of costly, redundant, or
"cute" features that aren't truly necessary or that have simpler and
efficient alternatives. A good example of this is using exception
handling as an alternative to while and for statements. Similarly,
templatizing a class or functions that are never used for more than a
single type is an expensive and redundant task. "Cute" features and
puns such as macro trickery can also result in indecipherable code.
However, the worst of all is probably a wheel's reinvention. Writing
custom container classes and algorithms instead of using the ones that
are readily available in C++ (in the form of STL) is expensive, bug
prone, and totally uncalled for. One of the phases of design and code
reviews should consist of catching and sifting such instances of
over-engineering, wheel reinvention, and "poetry." -------------------------------------------------------------------------------- STATIC INITIALIZATION AND DYNAMIC INITIALIZATION C++ distinguishes between two initialization types for objects with static storage duration (global objects, local static objects, and objects declared in namespace scope have static storage). Static initialization consists of either zero-initialization or initialization with a constant expression; any other initialization is considered dynamic initialization. These two types roughly correspond to compile-time initialization and runtime initialization. For example:
In other words, objects with static storage may be initialized
twice: once by static initialization, during which their memory
storage is initialized to binary zeroes, and afterwards, when their
constructors perform additional dynamic initialization. -------------------------------------------------------------------------------- SWAP WITHOUT A TEMPORARY The classic implementation of swap looks like this: void swap (int & i, int & j) Is it possible to swap i and j without using a third variable? Yes, it is: swap(int & i, int & j) However, this version isn't more efficient than the previous one (in fact, it's probably less efficient) because the compiler must generate a temporary variable to evaluate the expression i = (j - i). Although it has no practical value, this exercise is not completely
useless: it may show up in your next job interview, so it's worth
remembering how to solve it. -------------------------------------------------------------------------------- THE HIDDEN COST OF A VIRTUAL FUNCTION You're probably aware of the overhead associated with calling a
virtual member function. However, the performance penalty might even
be higher than it first seems. Usually, compilers can't inline a
virtual function call. Therefore, the performance cost consists of the
inability to inline a function plus the additional overhead of dynamic
binding. Many programmers aren't aware of the double overhead when
they declare member functions virtual. As a rule, don't declare a
member function virtual unless it really needs to be virtual. -------------------------------------------------------------------------------- THE LINKAGE TYPE OF GLOBAL CONST OBJECTS In C++ (but not in C), a const object declared in the global scope has internal linkage. This means that a const object that apparently looks like a global one is visible only in the scope of its source file; it isn't visible from other files unless you explicitly declare it extern:
const int x=0; /* File b.cpp */ /* File main.cpp */ Public symbol x defined in both module a.obj and b.obj This is because x has external linkage when it's not declared const. As a result, the two definitions of x clash. There are two lessons from this example. First, if you wish to make a const object declared in the global scope globally accessible, you must declare it extern. Second, avoid declaring nonlocal const objects static, as in static const int x = 0; This is both redundant and deprecated. -------------------------------------------------------------------------------- THE NAMED RETURN VALUE OPTIMIZATION The named return value (NRV) is an automatic optimization that compiler applies to eliminate the construction and destruction of an object returned by value from a function. When is the NRV applied? When a temporary object is copied to another object using a copy constructor, and none of these objects is const or volatile, C++ allows the implementation to treat the two objects as one and not perform a copy at all. For example:
A f() /* returns A by value */ A a2 = f(); -------------------------------------------------------------------------------- THE PRESENT AND FUTURE OF LONG On 32-bit platforms, int and long usually occupy the same size.
Programmers use these types interchangeably. However, on 64-bit
architectures, long is often a 64-bit integer, whereas int occupies 32
bits. In the next few years, many platforms and processors will switch
to a 64-bit word size. To avoid future maintenance headaches and
portability problems, use plain int or an equivalent 32-bit typedef
when you need a 32-bit data type. If you really need a 64-bit sized
integer, use either long long or a nonstandard type such as __int64.
These types have explicit sizes, so they are more portable than plain
long. -------------------------------------------------------------------------------- THE RANDOM_SHUFFLE ALGORITHM The STL random_shuffle() algorithm shuffles the elements of a sequence. This algorithm takes two arguments: an iterator that points to the first element of the sequence and an iterator that points one position past the last element of that sequence. The following program defines a vector of characters and fills it with four consecutive elements. Next, random_shuffle() is called to shuffle the elements in the vector:
int n[4] = {0,1,2,3}; -------------------------------------------------------------------------------- THE RELEVANCE OF THE SINGLE EXIT POINT IDIOM Many programming schools recommend that functions have only one exit point. Although the "single exit point" idiom has some merits in procedural languages, it's virtually useless in C++. To see why, look at this function. Outwardly, it complies with the single exit idiom: int func() Although it may not be immediately visible, func() has three exit
points, one of which is an explicit return statement. The other two
are implicit: In statement #1, if new fails, it throws a
std::bad_alloc exception (throwing an exception is an exit point).
Similarly, in statement #2, if string's constructor fails, it also
throws an exception. Thus, the single exit point design idiom is
irrelevant in nontrivial C++ code. Instead of following obsolete
principles, it's best to focus on robust, efficient, and functional
design. -------------------------------------------------------------------------------- THE REPLACE() ALGORITHM STL's replace() algorithm is defined in the standard header <algorithm> and has the following prototype: void replace(ForwardIterator start, The first and the second arguments mark the sequence's beginning and end, respectively. replace() changes every occurrence of old_value in the sequence to new_value. The following example uses replace() to replace every occurrence of "NT" with "Win2000" in a vector of strings: #include < algorithm > You can apply replace() to built-in arrays, too: int n[]={1,0,2,1}; -------------------------------------------------------------------------------- THE STORAGE OF EXCEPTION OBJECTS Consider the following program:
Although the C++ standard doesn't specify where exceptions are
stored in memory, the general approach among compiler vendors is to
use a special stack for exceptions. This stack is not affected by the
stack-unwinding process. Therefore, when an exception is thrown from a
function and that function exits, the exception object remains alive
until the handler that caught it terminates. -------------------------------------------------------------------------------- THE UNDERLYING REPRESENTATION OF POINTERS TO MEMBERS Although pointers to members behave very much like ordinary pointers, behind the scenes, they aren't necessarily represented as pointers. In fact, a pointer to a member is usually a struct that can contain up to four members. This is because pointers to members don't support only ordinary member functions; they also support virtual member functions, member functions of objects that have multiple base classes, and member functions of virtual base classes. The simplest member function can be represented as a set of two pointers: one holding the physical memory address of the function, and a second pointer that holds that pointer. However, in cases like a virtual member function and multiple inheritance, the pointer to a member must store additional information; hence, its underlying structure gets larger and more complex. Therefore, you cannot cast pointers to members to ordinary pointers, nor can you safely cast a pointer to a member of one type to another; in both cases, the results are undefined. To get an idea of how your compiler represents pointers to members, you can measure their size. In the following example, the pointers to data members have different sizes and, hence, different representations (of course, the results may vary across platforms and compilers):
-------------------------------------------------------------------------------- THE USE OF VIRTUAL DESTRUCTORS By design, std::string, std::complex, and all STL containers do not have a virtual destructor or virtual member functions. The lack of a virtual destructor means one thing: This class shouldn't be used as a base class. Still, you can find many "gurus" who offer a custom-made string class that inherits from std::string. This is a bad idea, though. To see why, consider the following program: #include < string > class Derived: public string /* bad idea */ { Class Derived is inherited from std::string. It has a destructor,
which decrements the value of destr. Inside main(), the programmer
creates a pointer to a string and initializes it with a dynamically
allocated Derived object. The next line is problematic. Seemingly, the
delete expression destroys the Derived object and invokes its
destructor. However, if you examine destr, you will discover that its
value remains unchanged. This is because Derived's destructor was
never called! Consequently, the program's behavior is undefined. To
conclude, deriving from std::string--or any class that doesn't have a
virtual destructor--is bad and dangerous. -------------------------------------------------------------------------------- USE EXPLICIT UNSIGNED TO INDICATE BINARY DATA Whether char is signed or unsigned is compiler-dependent. Some compilers treat the array char arr[100]; as 100 unsigned characters; others treat it as 100 signed characters. The unsigned quality of char is important when you're working with raw binary data. To ensure portability, always add an explicit unsigned qualifier when you declare char variables that store binary data: unsigned char mp3_clip[KB*KB]; /* 1 Mb buffer*/
USES OF AN ASSOCIATIVE ARRAYAn associative array stores pairs of values--one serves as the key and the other is the associated value. STL's map is an example of an associative array. In the following example, it stores pairs of strings and enumerated values. The string is the key whose associated value is an int. Some symbolic debuggers associate enum values with their textual description using a technique similar to the following:
int main()
USING OBJECTS WITH NO DEFAULT CONSTRUCTOR AS CLASS MEMBERSSuppose you have a class called A that doesn't have a default constructor, and you need an instance of this class as a member of another class, B: class A When you instantiate an ordinary A object, you pass an argument to its constructor: A a(3); However, you can't do that when A is a member of another class: class B Instead, use a member-initialization list in the containing class's constructor to pass an argument to the embedded object's constructor: class B
USING THE SORT() ALGORITHMThe generic algorithm sort() sorts a sequence of elements. Sort() takes two iterators that point to the beginning and the end of a sequence:
int main()
VECTORS AND CONTIGUOUS MEMORYA built-in array is stored on a contiguous block of memory. Because of an oversight, the C++ Standard doesn't officially guarantee that vector elements also occupy contiguous memory. This isn't really a problem because all current STL implementations use contiguous memory for vectors, and the requirement that vectors occupy contiguous memory will be added to the C++ Standard in the next revision.
VOLATILE CONTAINER OBJECTSVolatile objects are used in multithreaded applications and applications that map hardware devices into registers. Although you can declare an STL container object with the volatile qualifier, you can't use any of its member functions safely. The problem is that STL containers don't have any volatile member functions. Calling a nonvolatile member function on a volatile object is ill formed: volatile vector < int > vi; Because of the attempt to call a nonvolatile member function, some compilers compile this code with warnings; others issue an error message. Calling a nonvolatile member function from a volatile object yields unpredictable results, because the state of the object might be changed by another thread in between. Is there a way to get around this? No, there isn't. By design, STL containers don't include volatile member functions because of the performance penalty and implementation complexity associated with volatile semantics.
VOLATILE MEMBER FUNCTIONSYou're probably familiar with const member functions such as
int main()
WHERE DEFAULT ARGUMENTS CAN APPEARDefault arguments can be specified only in the parameter list of a function declaration or in a template-parameter. This means that default arguments cannot appear in declarations of pointers to functions, pointers to member functions, references to functions, or typedef declarations. For example:
/* the following declarations are all ill-formed */
WHO IS THISIn the body of a nonstatic member function, the keyword "this" is a non-lvalue expression whose value is the address of the object for which the function is called. By contrast, in pre-standard C++, "this" was a pointer. The difference between a real pointer and a "non-lvalue expression whose value is the address of the object" might seem like hair-splitting. However, the distinction between the two is important because the new definition guarantees that programmers can't assign a new value to "this" (assigning to "this" was valid programming practice in the preliminary stages of C++). In other words, you can think of "this" as a function returning the address of its object, rather than the pointer itself.
ZERO INITIALIZATIONTo zero-initialize an object of type T means that the memory storage occupied by the object is set to binary zeros. More precisely, if T is a built-in data type, an enumeration type, or a pointer type, the storage is set to the value of 0 converted to T. For aggregates and class objects, zero-initialization means one of the following: * For a class type, the storage for each data member and each
base-class subobject is zero-initialized. If T is a reference type, no initialization is performed because there are no null references in C++.
HIDE FUNCTION POINTER DECLARATIONS WITH A TYPEDEFCan you tell what the following declaration says? void (*p[10]) (void (*)() ); It declares p as an array of ten pointers to a function returning void and taking a pointer to another function that returns void and takes no arguments. The cumbersome syntax is nearly indecipherable. However, you can simplify it considerably by using typedef declarations. First, declare a typedef for "pointer to a function returning void and taking no arguments" as follows: typedef void (*pfv)(); Next, declare another typedef for "pointer to a function returning void and taking a pfv" based on the typedef we previously declared: typedef void (*pf_taking_pfv) (pfv); Using the typedef pf_taking_pfv as a synonym for the unwieldy "pointer to a function returning void and taking a pfv," declaring an array of ten such pointers is a breeze: pf_taking_pfv p[10];
THE MAXIMAL MUNCH RULEEvery compiler has a tokenizer, which is a component that parses a source file into distinct tokens (keywords, operators, identifiers, etc.). One of the tokenizer's rules is called "maximal munch," which says: Keep reading characters from the source file until adding one more character causes the current token to stop making sense. For example, if the letters c, h, and a have been read and the character that follows is r, the tokenizer will read it and complete the token char. In certain contexts, the maximal munch rule can have surprising effects, though. Consider the following declaration: vector < stack < int >> vs; /*error*/ The programmer wanted to declare a vector of stacks. However, the tokenizer didn't parse this declaration correctly. Because of the maximal munch rule, the sequence >> is parsed as a single token (the right shift operator) rather than two template argument list terminators. At a later stage, the syntactic analyzer detects that a right shift operator doesn't make sense in this context and the compiler will issue an error message. To fix this, insert a space between the two >>. The tokenizer treats whitespaces as token terminators. Therefore, it will now parse the following declaration correctly: vector < stack < int > >; /*now OK*/
FLOATING POINT LITERALSIn the expression if (ticket_price > 7.50) what's the type of the literal 7.50? You might think that it's float. However, the actual type is double. To force the compiler to store a floating point literal as a float rather than double, you have to append the affix f to it: if (ticket_price > 7.50f)
IDEMPOTENT TYPE QUALIFIERSThe type qualifiers const and volatile are idempotent. This means that if a type qualifier is included indirectly several times in a type specification (through a typedef or a template parameter), it's treated as if it appeared only once. For example: typedef const int CI; The typedef CI is a synonym for const int. Now suppose we declare another typedef based on CI: typedef const CI CCI; CCI is a synonym for const CI, which is actually const const int. The compiler ignores the redundant const and treats CCI as a synonym for const int. For example: int f(CCI n); /*fine, f takes a const int*/ Remember that this rule applies to indirect inclusion of a type qualifier; direct repetitions are illegal: void f(const const int); /*ill-formed*/ In C99, the newly approved C standard, this restriction was relaxed and even direct repetitions are allowed.
THE LOCATION OF TEMPLATE DEFINITIONSNormally, you declare functions and classes in an .h file and place their definition in a separate .cpp file. With templates, this practice isn't really useful because the compiler must see the actual definition (that is, the body) of a template, not just the declaration, when it instantiates a template. Therefore, it's best to place both the template's declaration and definition in the same .h file. This is why all STL header files contain template definitions. In the future, when compilers support the "export" keyword, it will be possible to use only the template's declaration and leave the definition in a separate source file.
A FREE XML PARSERXML4C is a validating XML parser written in C++. It enables an application to read and write XML data. XML4C is compliant with the XML 1.0 specification. Source code, samples, and API documentation are provided with the parser, for free. You can download it (registration is required) from http://www.alphaworks.ibm.com/tech/xml4c CORRECTION http://www.topica.com/lists/tipworld-cplusplus/read
A GENERIC CALLBACK DISPATCHERYou can create a generic callback class template to automate member functions' callback. Such a template takes the class whose member function is to be called as its first parameter. The second parameter is a pointer to that class's member function. The trick is that you base the second parameter on the first one, as follows: template < class T, void (T::*F)() > class callback {/**/}; The implementation of the template is relatively simple: It has a reference to T, which is the class whose member function is to be called, a constructor and a member function called execute(): template < class T, void (T::*F)() > To call a member function through a pointer to member, you must have a reference or pointer to the actual object. This is why the template has a T& as a member. Now suppose you want to use the callback template to execute a member function of class A: class A You can't use a variable as the address of the member function. Instead, use the & operator to take the function's address. Finally, pass the object whose member function you want to be called as the argument for the template object: int main() You can use the callback class template with any class type, as long as the member function called has the same signature.
A PORTABLE DATA FORMATData types larger than the size of a byte aren't portable because of the different endian-ordering of hardware architectures. Therefore, passing an int in its binary form from an Intel-based machine to a UNIX machine or vice versa requires that you reverse the integer's byte before the target machine can read it. Sometime, reversing the byte ordering is too expensive or too complicated. Worse yet, there are several ordering options: little endian, big endian, middle endian, and so on. It's not always possible to predict the exact byte ordering on the target machine. Is there a workaround? Yes, there is. Pass all data variables in their textual representation. For example, instead of passing the integer 0 as a sequence of four bytes, pass the string 0. Another example: Instead of passing the integer 32 as a sequence of the four bytes 0x0, 0x0, 0x0, 0x20, pass the literal string 32 instead. Char arrays are portable, and they do not require order reversing.
A REFERENCE TO A REFERENCE IS ILLEGALWhat is wrong with this code snippet? #include < string > class Node {/*..*/}; int main() If you try to compile this example, your compiler will issue numerous compilation errors. The problem is that list < T > has member functions that take or return T&. In other words, the compiler transforms < node & > to < node && >. Since a reference to a reference is illegal in C++, the program is ill formed. As a rule, you should instantiate templates in the form of list < node > and never as list < node & >.
A SHORTHAND FOR INTERNATIONALIZATIONThe software industry uses the term I18N as an abbreviation for "internationalization." The idea is that I18N begins with an i, ends with an n, and has 18 characters in between. This way, one avoids the unwieldy term and its different spelling conventions (internationalisation in British English). Likewise, "localization" is abbreviated to L10N. When you're consulting your compiler's online help or run a query on a search engine regarding internationalization and localization of C++ apps, try these abbreviations.
ACCESSING MEMBERS WITH STATIC MEMBER FUNCTIONSA static member function doesn't take an implicit "this." Therefore, it can't access any other members of its class unless they are also static. Sometimes you have no choice but to use a static member function, but you still need to access other members of the class from that function. There are two solutions. One solution is to declare these members static, so that the static member function can access them directly. For example: class Singleton Singleton * Singleton::instance() Alternatively, pass the static member function a reference to the object so that it can access its members: void C::func( C & obj) /* a static member function*/
ADDING OBJECT-ORIENTED FUNCTIONALITY TO A POD TYPESometimes, legacy code that contains Plain Old Data (POD) types can use an object-oriented face-lift, such as adding a constructor or member functions. Instead of adding the new functionality into the POD struct itself, it's better to derive a new class from it and add the new object-oriented functionality--member functions, constructor, and destructor--to the newly derived class. Leaving the POD struct intact will ensure binary compatibility with other code components that still use the original struct.
ARGUMENTS AND PARAMETERSARGUMENTS AND PARAMETERS The terms "argument" and "parameter" are often used interchangeably in the literature, although the C++ Standard makes a clear distinction between the two. An argument is one of the following: an expression in the comma-separated list in a function call A parameter is one of the following: an object or reference that is declared in a function declaration
or definition The following example demonstrates the difference between a parameter and an argument: void func(int n, char * pc); /* n and pc are parameters */ int main() What is wrong with this code snippet? #include &< string &> class Node {/*..*/}; int main() If you try to compile this example, your compiler will issue numerous compilation errors. The problem is that list &< T &> has member functions that take or return T&. In other words, the compiler transforms &< node & &> to &< node && &>. Since a reference to a reference is illegal in C++, the program is ill formed. As a rule, you should instantiate templates in the form of list &< node &> and never as list &< node & &>.
ARRAYS OF LITERAL STRINGSDoes your compiler accept the following code? char s[3]="abc"; /* illegal; no place for '\0'*/ It shouldn't. The standard requires that the size of a char array initialized with a literal string be sufficiently large to contain a terminating null. The declaration should be flagged as a compilation error because s has three elements instead of four. However, many C++ compilers still follow the array rules of C, which permit char arrays without a terminating null.
ASSIGNING A SPECIFIED MEMORY ADDRESS TO A POINTERIn low-level programming and hardware interfaces, you often need to assign a pointer to a specific physical address. To do that, cast the address value using the reinterpret_cast operator. Here's an example: void *p;
ASSIGNING INTEGERS TO ENUM VARIABLESC and C++ differ in their handling of enum types. While C allows you to assign a plain int to an enum variable, C++ doesn't. Therefore, a C compiler will accept the following code while a standard compliant C++ compiler won't: enum Direction (West, North East, South}; C++ has stricter type safety rules. A standard-compliant C++ compiler will reject the assignment of 1 to d. You have to use an explicit cast for this to work, or better still, always assign an enumerator to an enum variable: d = static_cast < Direction > (1); // fine
AVOID ASSIGNMENTS INSIDE AN IF CONDITIONAn assignment expression can appear inside an if condition: if (x = getval() ) The if condition is evaluated in two steps: First, the unconditional assignment to x takes place. Then, x is checked. The if-block is executed only if x's value (after the assignment) isn't 0. Although this technique can save you a few keystrokes, it's highly dangerous and should be avoided. The problem is that one can easily mistake == for = or vice versa. For this reason, several compilers issue a warning message if you place an assignment expression inside an if condition, to draw your attention to a potential bug. If you need to assign a value and test the result, separate these steps into two distinct statements: x = getval(); This way, you document your intention more clearly and avoid this potential bug.
AVOIDING MEMORY FRAGMENTATIONOften, applications that are free from memory leaks but frequently allocate and deallocate dynamic memory show gradual performance degradation if they are kept running for long periods. Finally, they crash. Why is this? Recurrent allocation and deallocation of dynamic memory causes the heap to become fragmented, especially if the application allocates small memory chunks. A fragmented heap might have many free blocks, but these blocks are small and noncontiguous. To demonstrate this, look at the following scheme that represents the system's heap. Zeros indicate free memory blocks, and ones indicate memory blocks that are in use: 100101010000101010110 The above heap is highly fragmented. Allocating a memory block that contains five units (that is, five zeros) will fail, although the system has 12 free units in total. This is because the free memory isn't contiguous. On the other hand, the following heap has less free memory, but it's not fragmented: 1111111111000000 What can you do to avoid heap fragmentation? First, use dynamic memory as little as possible. In most cases, you can use static or automatic storage or use STL containers. Second, try to allocate and deallocate large chunks rather than small ones. For example, instead of allocating a single object, allocate an array of objects at once. As a last resort, use a custom memory pool.
BINDING A REFERENCE TO AN RVALUEBinding a reference to an rvalue is allowed as long as the reference is bound to a const type. The rationale behind this rule is straightforward: you can't change an rvalue; only a reference to const ensures that the program doesn't modify an rvalue through its reference. In the following example, the function f() takes a reference to const int: void f(const int & i); The program passes the rvalue 2 as an argument to f(). At runtime, C++ creates a temporary object of type int with the value 2 and binds it to the reference i. The temporary and its reference exist from the moment f() is invoked until it returns; they are destroyed immediately afterward. Note that had we declared the reference i without the const qualifier, the function f() could have modified its argument, thereby causing undefined behavior. The same rule applies to user-defined objects. You may bind a temporary object only to a const reference: struct A{};
BLOCKING FURTHER INHERITANCE OF A CLASSTo block a class from being derived any further, declare its constructor private. Next, add a public static member function Instance() that creates an instance of this class and returns its address. Calling Instance() is the only way to create instances of such a class, because the constructor is otherwise inaccessible. Here's an example: class A int main()
BOOLEAN LITERALSThe Boolean literals "false" and "true" evaluate to 0 and 1, respectively. In other programming language, true and false may have different values. Visual Basic, for instance, defines false as -1 and true as 1. C++ programmers who write code that interfaces with other languages sometimes discover this fact the hard way. Therefore, don't assume that "true" and "false" have the same representation in all languages and environments.
C++ NAMING CONVENTIONSIf you're looking for a "house style" of naming conventions, you can adopt the one used in Standard C++. It uses all lowercase letters for identifiers, and underscores as word separators. According to this naming convention, a function that cleans a stack, for example, would be named void clean_stack(); /* ANSI/ISO C++ naming style */ rather than void CleanStack(); /* MFC style */ Class names in the Standard Library follow this convention too: class std::type_info {/*..*/}; /* class name */ Even constants have all lowercase names: ios::binary Remember that the naming convention you pick isn't crucial; what's important is that you use it consistently.
CACHE THE RESULT OF COMPLEX COMPUTATIONSThe following loop is very inefficient: for (int j=0; j < strlen(s); ++j) On each iteration, it invokes the function strlen(). You can improve this loop's performance by caching the result of strlen() and using the cached value in the loop: const int length = strlen(s); /*cache the result*/
CALCULATING THE POWER OF TWO NUMBERSThe Standard Library provides the pow() functions (declared in < cmath >), which takes two arguments of type double: double powl(double x, double y); pow() returns the value calculated of x to the power of y. For example: double x=10, y=4;
CALCULATING THE SIZE OF AN INCOMPLETE ARRAYAn incomplete array declaration can appear in a function's parameter list. For example: int count(const char s[]); The declaration of s doesn't include the array's size. Can count() use the operator sizeof to calculate s's size? No, it can't. The compiler implicitly transforms an array into a pointer. Therefore, sizeof returns a pointer's size, not an array's size. One way to obtain the array's size is to pass an additional argument that holds the number of elements in the array: int count(const char s[], int arr_size); However, a better solution is to pass a vector and call its size() member function: int cout(const vector < char > & s )
CALCULATING TIME DIFFERENCESTo compute the time that elapsed between two dates, use the standard function difftime(). This way, you avoid potential bugs such as leap years or Y2K issues. difftime() takes two variables of type time_t, each representing a date, and returns the number of seconds that elapsed between them. For example: #include < ctime >
CALLING A FUNCTION BEFORE PROGRAM'S STARTUPCertain applications need to invoke startup functions that run before the main program starts. For example, polling, billing, and logger functions must be invoked before the actual program begins. The easiest way to achieve this is by calling these functions from a constructor of a global object. Because global objects are conceptually constructed before the program's outset, these functions will run before main() starts. For example: class Logger int main() The global object log is constructed before main starts. During its construction, log invokes the function log_user_activity(). When main() starts, it can read data from the log file.
CALLING A FUNCTION THROUGH A POINTERTo call a function through a pointer, treat that pointer as if it were the function itself. For example: #include <cstring>
CALLING A MEMBER FUNCTION THROUGH A POINTER TO MEMBERThe .* and ->* operators enable you to call a member function without having to know the function's name. Remember to parenthesize the function call as follows: A a; /*create object*/ /*the two pairs of parentheses are mandatory*/ Omitting the first pair of parentheses, as in the following example, is a common mistake: a.*pmf(); /*error, missing parentheses*/ Although some compilers accept it for an unknown reason, this is not the correct C++ syntax.
CALLING OVERLOADED OPERATORS EXPLICITLYThe overloaded operator mechanism is "syntactic sugar" for ordinary function calls. You can always use the explicit name of an overloaded operator to resolve ambiguities and document your intention explicitly. For example, the statement cout << "hello world"; can be rewritten as follows: cout.operator<<("hello world"); Instead of using the operator's sign directly, you can use a combination of the keyword 'operator' followed by the operator's sign and its argument list. Another example: Instead of writing string s; you can write empty = s.operator==(""); /* compare *p to an empty string*/ Although you wouldn't normally use this unwieldy form, you should be familiar with this notation because some compilers and linkers issue error messages that contain the explicit operator function name.
COMMA-SEPARATED EXPRESSIONSAn expression may consist of one or more subexpressions separated by commas. For example: if(++x, --y, cin.good()) /*three expressions*/ The if condition contains three expressions separated by commas. C++ ensures that each of the expressions is evaluated and its side effects take place. However, the value of an entire comma-separated expression is the result of the rightmost expression. Therefore, the if condition above evaluates as true only if cin.good() returns true. Here's another example of a comma expression: int j=10;
COMPUTING THE MAXIMUM OF THREE VALUESThe std::max() template takes two values and returns the highest. What if you need to calculate the maximum of three values? Don't write a special function template for this purpose. Instead, apply max() to two arbitrary values of the three, and then apply max() once again to the result and the third value: int l=10, m=11, n=7;
CONSTANT EXPRESSIONSC++ requires the use of integral constant expressions in various contexts; for example, when you define arrays' sizes, case labels, bit-field sizes, and enumerator initializers. An integral constant expression is one of the following: * A numeric literal, as 100 in x < 100; * Enumerators such as up and down in the following enum type: enum state {up, down}; * Const variables or static data members of integral or enumeration types initialized with constant expressions--for example, x in this definition: const int x = 0; * Non-type template parameters of integral or enumeration types: S < 100 > s; * Finally, a sizeof expression: sizeof (int);
COPYING FILESTo copy the contents of one file into another, use the fstream classes. First, open the source file using an ifstream object, then create a target file using ofstream, and finally, read the source into the target using the rdbuf() member function: #include < ftsream > Note that you don't have to call the close() member function because the fstream objects do that automatically when their destructors are invoked. However, you want to call close() if other parts of the program access the files before the fstream objects go out of scope.
COUNTING "LIVE" INSTANCES OF A CLASSCounting the number of instances of a certain class is often necessary in debugging and performance tuning. To do that, declare a static data member as a counter, and have the constructor and copy constructor increment it and the destructor decrement it. Finally, add a static member function that returns the counter's value: class A int A::count; /*static member definition*/ Because static members are automatically zero-initialized, you can call this function and get the correct results even if no instance of that class exists: int main()
CREATING IMMUTABLE CONSTANTSAlthough usually I don't recommend using macros in C++ code, there are a few exceptions. One such exception is when you want to create a truly immutable constant. Consider this constant: const int MAX=512; Seemingly, MAX's value can't be changed because it's const. However, on some platforms, a brute-force cast can change its value. For example: int *p = const_cast < int * > ( & MAX); Any attempt to change a const object causes undefined behavior. However, some implementations let this hack pass unnoticed. To disable it altogether, you can use a macro instead of a const object: #define MAX 512 This time, MAX can't be changed.
DECIDING IF LEARNING C IS A WASTE OF TIME"I'm learning C at school/college/from a book. Am I wasting my time learning this language? Should I be learning C++ instead?" is a frequently asked question. Although I usually recommend learning C++ right from the start, learning C is definitely not a waste of time. C is still a very useful, widely used, and highly capable language. It's been around for 27 years now and will remain one of the most popular programming languages in the foreseeable future. Consider these facts: C is the language in which every modern operating system is
written: All Windows flavors, including the recently shipped Win2000,
UNIX, Linux, BeOS, and others are coded in C, as are many APIs and
standards, such as POSIX.
DECLARING A TEMPLATE SPECIALIZATION AS A FRIEND OF A CLASS TEMPLATEYou can declare a template specialization as a friend of a class template. In the following example, the class template Vector declares the specialization C < void* > as its friend: template < class T > class C{/*...*/}; Each specialization of Vector has the specialization C < void* > as a friend. However, all other specializations of C, such as C < int >, are not friends of Vector.
DECLARING VARIABLES INSIDE AN IF-CONDITIONC++ allows you to declare variables just before their use rather than at the top of the enclosing block. For example, you may declare a variable inside the condition of an if-statement: class Base {/*..*/}; The advantage of declaring the pointer pd locally is obvious: It's always initialized properly, and it isn't visible to other parts of the program that shouldn't use it.
DEEP COPYING AND SHALLOW COPYINGThe terms "deep copy" and "shallow copy" refer to the way objects are copied during the invocation of a copy constructor or assignment operator. In a deep copy (also called "memberwise copy"), the copy operation respects object semantics. For example, copying an object that has a member of type std::string ensures that the corresponding std::string in the target object is copy-constructed from the target's string object: class A When assigning b to a, the compiler-generated assignment operator of class A first invokes the assignment operator of class std::string. Thus, the members a.s and b.s are well defined. On the other hand, a shallow copy (also called a "bitwise copy") simply copies chunks of memory from one location to another. A memcpy() operation is an example of a shallow copy. Because memcpy() does not respect object semantics, it won't invoke the copy constructor of an object. Therefore, you should never use memcpy() to copy objects. Use it only when copying POD (Plain Old Data) types: ints, floating point numbers, and dumb structs.
DELETING A FILEThe standard function remove() deletes a file. It takes one argument of type const char* that contains a filename. You can provide a full path as a filename. On success, remove() returns 0. A non-zero return code indicates an error. For example: #include < cstdio >
DELETING MULTIDIMENSIONAL ARRAYSYou can allocate a multidimensional array using new as follows: class A int m; The program allocates a three-dimensional array of A's called pa. How do you delete a dynamically allocated multidimensional array? It's simple: No matter how many dimensions the array has, you always use delete[] to destroy it: delete[] pa;
DIFFERENCES BETWEEN POSTFIX AND PREFIX OPERATORSThe built-in ++ and -- operators can appear on both sides of their operand: int n=0; You probably know that a prefix operator first changes its operand before taking its value. For example: int n=0, m=0; In this example, n equals 1 after the assignment because the increment operation took place before m's value was taken and assigned to n. By contrast, int n=0, m=0; In this example, n equals 0 after the assignment because the increment operation took place after m's original value was taken and assigned to n. To better understand the difference between postfix and prefix operators, examine the disassembly code generated for these operations. Even if you're not familiar with assembly languages, you can immediately see the difference between the two--simply notice where the inc (increment) assembly directive appears: /*disassembly of the expression: m=n++;*/ /*disassembly of the expression: m=++n;*/
DIFFERENCES IN INITIALIZATION RULES BETWEEN C AND C++Consider the following program: int func(); A C compiler will reject the declaration of arr because the initialization list doesn't contain constant expressions. By contrast, a C++ compiler will accept it without a hitch. You're probably thinking that this isn't an issue because you're using a C++ compiler anyway. However, many C++ compilers rely on a source file's extension to determine whether the code therein should be treated as C or C++. Usually, a .c extension invokes the C compiler, whereas a .cpp extension invokes the C++ compiler. Because the rules regarding initialization are somewhat different in both languages, the program above may or may not compile, depending on the file extension used. In C, the initializer list must contain constant expressions exclusively. In C++, you can use any valid expression as an initializer, including a function's return value or a previously declared variable. Therefore, when porting legacy code, remember to check the file extensions as well. (Note: The recently approved C99 now complies with the C++ initialization rules in this regard. Thus, the array above is well formed in C99 but not in older versions of the C standard.)
DIFFERENCES PROTOTYPES OF STANDARD FUNCTIONSA small number of functions from the Standard Library have different signatures in C and C++. These functions are: strchr(), strpbrk(), strrchr(), strstr(), and memchr() as well as their wide-character counterparts: wcschr(), wcspbrk(), wcsrchr(), wcsstr(), and wmemchr(). They are declared in the standard header <string.h> (or < cstring > in C++). Let's look at strstr(). While in C, strstr() has the following prototype: char* strstr(const char* s1, const char* s2); /*ANSI C*/ In C++, this function has two different prototypes that are incompatible with the C version: char* strstr(char* s1, const char* s2); /*ANSI C++*/ Another example: the function strpbrk(). In C, it has this signature: char* strpbrk(const char* s1, const char* s2); /*ANSI C*/ In C++, it has two different signatures: char* strpbrk(char* s1, const char* s2); /*ANSI C++*/ const char* strpbrk(const char* s1, const char* s2); /*ANSI C++*/ Can you see the pattern here? The C function takes "const X *" and returns "X *" whereas C++ defines two functions, one taking "X *" and returning "X *", and another taking "const X *" and returning "const X *". Pay attention to these subtle differences when you port code from C to C++ or vice versa.
DIFFERENT INITIALIZATION FORMS OF OBJECT MEMBERSConsider the following class: class A You can initialize the member 'size' in at least three forms: A::A (int size) : size (size) { } All of these forms are legal and portable. However, the first one is preferable because the member initialization notation explicitly indicates that you're initializing a data member. As for using identical names for the data member and the argument, the compiler is clever enough to distinguish between the two correctly. However, to avoid confusing a human reader, you may consider different names, as in A::A (int sz) : size (sz) { }
DISABLING COPYING AND ASSIGNMENT OF AN OBJECTTo block copying and assignment of a class object, explicitly declare its assignment operator and copy constructor private. Don't define them; only declare them. In this example, class A has a public default constructor. In addition, it declares a private copy constructor and assignment operator to ensure that objects of its class can't be copied or assigned to: class a
DUPLICATE NAMESPACE ALIASESA namespace alias can redefine an existing namespace alias, as long as the new definition refers to the same namespace as the old one. In other words, you're allowed to repeat an existing definition of a namespace alias, but you cannot change the meaning of a previously defined namespace alias. For example: namespace ATL {/*...*/} This rule enables you to place the same namespace alias definition in every source file of the same project.
DYNAMIC TYPE AND STATIC TYPEThe type of the most derived object to which an expression refers is said to be the dynamic type of that expression. For example, if p is declared as a pointer to class B but it actually points to an object of class D (where D is derived from B), the dynamic type of the expression *p is D whereas its static type is B: B * p; /*static type of *p is B*/ The same is true for references: void f(B & b); /* b's static type is B&*/
ELIMINATING BUFFER OVERFLOWSBuffer overflows are a fertile source of bugs and malicious attacks. They occur when a program attempts to write data past the end of a buffer. Consider this example: #include < cstdio > The program reads a string from the standard input (the keyboard). However, it doesn't check the string's length. If the string has more than 14 characters, it causes a buffer overflow as scanf() tries to write the remaining characters past buff's end (remember that one character is always reserved for a null terminator). The result is most likely a runtime crash. On some systems, the users will receive a shell's prompt after the crash. Even if the shell has restricted privileges, end users can still examine environment variables and list the current directory files. Although the program uses C-style I/O operations, such code is still widely used today. Even if you can't leverage legacy code with a more object-oriented approach, there are still some techniques to avert such overflows. First, always check the array's bounds before writing it to a buffer. If this is impossible--such as when the input is coming from a CGI script--use functions that explicitly limit the number of input characters. For example, instead of using scanf(), use the fgets() function, which reads characters up to a specified limit: char buff[15] = {0}; Additionally, the standard string functions have versions that take an explicit size limit. Instead of strcpy(), strcmp(), and sprintf(), use strncpy(), strncmp(), and snprint(), respectively. Next time, we will see how to avoid overflows using a more object-oriented approach.
ENHANCING VECTOR'S PERFORMANCEstd::vector allocates storage for its elements on demand. This is advantageous because it frees you from the hassles of manual memory management. However, in real-time environments, frequent reallocation of memory can incur unacceptable overhead. If you know the total number of elements that are to be stored in advance, you can preallocate storage for them at once using the reserve() member function: int main() reserve(n) allocates storage for at least n elements (not bytes), thereby ensuring that the container doesn't reallocate memory as long as the number of elements doesn't exceed n.
ENUM TYPES ARE ORDINARY TYPESYou can use an enum type as you would use any other type. You can create arrays, allocate it dynamically using operator new, overload operators for it, create pointers, and return it from a function: enum Dir Dir get_dir(); /* in a function's return type */ int main()
EQUALITY TESTS ON LITERAL STRINGSIt is perfectly legal to use the equality operator to compare literal strings. However, the results might be surprising: bool eq; Let's parse the following expression carefully: "Mungojerrie" == "Rumpleteazer"; The literal strings "Mungojerrie" and "Rumpleteazer" are both of type const char *. Thus, the expression checks whether the two memory addresses of the literal strings are identical and returns a Boolean result accordingly. However, the strings themselves are not compared, to the bafflement of the programmer. The right way of comparing literal strings is by using the standard function strcmp(). Alternatively, you can wrap the literal strings in two std::string objects and compare them.
EQUIVALENCE IN STLSTL containers and algorithms require that you overload operator < for the class type they store as elements. This is necessary for sorting and comparing these elements. STL synthesizes the equality operator from the < operator by using the following construct: bool operator==( const T & arg1, const T & arg2) In other words, you don't need to define an overloaded version of operator == (or any other relational operator) because STL knows how to generate these operators from operator <. Therefore, you only need to define an overloaded version of operator < for user-defined types.
ERASING ELEMENTS OF ASSOCIATIVE CONTAINERSThe associative containers std::map, std::multimap, std::set, and std::multiset have three overloaded forms of the member function earse(): class map /*simplified for brevity*/ The first version takes an iterator pointing at an existing container element and erases it. For example, the following code erases the last element of the container: employees.erase(employees.rbegin()); An element of an associative container is a pair of two values: a key and its associated value. The second version of erase() enables you to erase an element using its key. For example: std::map< int, std::string > employees; The third version of erase() deletes a sequence of elements whose bounds are indicated by the iterators first and last. For example, the following statement erases all the elements of a map: employees.erase(employees.begin(), employees.end());
ESCAPE SEQUENCESSeveral special characters are represented as escape sequences. An escape sequence begins with a \ (backslash) followed by an alphanumeric character. For example, the \n escape sequence represents the newline character. Note that the two characters of an escape sequence are construed as a single character. Here's a list of C++ escape sequences: \a /*alert (bell)*/
EXCEPTION SPECIFICATIONS ARE CHECKED AT RUNTIMEA function can specify explicitly what type of exception it might throw by proving an exception specification. Exception specifications are enforced at runtime. Therefore, the following code is perfectly legal: int f(); /*can throw any type of exception*/ g() promises not to throw. However, it calls f(), which might throw any type of exception. If f() indeed throws, the exception propagates through g(), thereby violating its guarantee not to throw. Still, the compiler doesn't reject this code because C++ enforces exception specification at runtime. There are several reasons for the runtime checking policy. In this example, f() could be a legacy C function. It's impossible to enforce a C function to have an exception specification. For this reason and others, C++ exception specifications are enforced at runtime.
EXTERN FUNCTIONSUnless explicitly declared static, an ordinary function is implicitly declared extern. For example: extern void func(int i); /*extern is redundant*/ However, sometimes the extern qualifier is added to a function declaration to document the fact that it has external linkage and can be called from any translation unit--for example: /* found in < string.h >*/ Do not confuse plain extern with extern "C". extern "C" indicates that an identifier has C linkage rather than C++ linkage, whereas plain extern merely guarantees that the identifier is globally visible.
EXTRACTING A NUMBER FROM A STRINGMany applications store numbers in strings that also contain text. For example, some databases store dates as strings in the form of "01Jan". You can extract the number from a string by using the standard function strtol() ("string to long"). In this example, strtol() extracts the number 14 from the string "14-Feb" and assigns that number to n: #include < stdio.h > In this example, the radix is decimal. You can specify any radix between 2 through 36 as the third argument of strtol(). Alternatively, you can let strtol() deduce the radix automatically according to the numeric characters it reads from the scanned string by supplying 0 as the radix value.
FAHRENHEIT TO CELSIUS CONVERSIONAlthough the Standard Library doesn't define functions that convert Fahrenheit degrees to Celsius degrees and vice versa, you can implement them by yourself. These functions are particularly useful in localization and internationalization projects. You may also find them useful in any international weather report Web site. Here are the two conversion functions: double to_celsius(double fahr) double to_fahrenheit(double cel)
FASTEST MINIMUM-WIDTH INTEGERSThe header < inttypes.h >, which was first available as a nonstandard extension, recently became an integral part of C99. It defines a set of typedef names called "fastest minimum-width integer types." Each of these names designates an integer type that is usually fastest to operate with among all integer types that have at least the specified width. These integers have the general form of int_fastn_t or uint_fastn_t, where n is the minimum required width. For instance, int_fast32_t is the fastest signed integer type having a width of at least 32 bits. The fastest minimum-width integer types are int_fast8_t int_fast16_t Their unsigned counterparts are uint_fast8_t uint_fast16_t What are they good for? Suppose you need a loop counter that needs no fewer than 16 bits. Ostensibly, you can use a variable of type short. However, under certain RISC architecture, the use of short is less efficient than int. To make sure that the counter's type is always the one with which the CPU operates optimally, use int_fast16_t instead of short. This way, you ensure that on every platform, the compiler uses the fastest integral type that has at least 16 bits: #include < inttypes.h >
FIXING A POTENTIAL BUG IN CTYPE FUNCTIONSFor efficiency reasons, most implementations use bitwise operators rather than relational operators to implement the <cctype> standard functions isalpha(), isdigit(), isprint(), and so on. Bitwise operations work well only when applied to unsigned values. However, on some implementations, char is signed by default. Consequently, these functions may produce incorrect results. To fix this, explicitly cast the argument to unsigned char. This will ensure that these functions produce the correct results, regardless of char's signedness: char c;
FLOATING-POINT ARITHMETIC MYTHSIn the olden days, the use of floating-point variables imposed significant computation and speed overhead compared to integer arithmetic. For this reason, many optimization guidebooks and IT veterans still use integers instead of floating-point data. However, on some modern processors (such as Pentium), floating type arithmetic is FASTER than integer arithmetic. Therefore, if you want to accelerate a computation-intensive application on such platforms, you should reverse this rule: Use floating-point data instead of integers. Don't get me wrong here--I don't recommend using floating point instead of integers as loop counters, for example. The lesson here is that you should experiment before applying any optimization measures--some of them might turn out to be counterproductive.
FLOATING-POINT NUMBERS REPRESENTATIONPeople sometimes complain about the inaccuracy of floating-point arithmetic. To demonstrate the level of floating-point inaccuracy, consider the following program: #include < iostream > On my machine, this program prints 0.579956 instead of 0.58. More complex calculations yield higher inaccuracy. What is going on here? Remember that rounding, approximation, and truncation are not the responsibility of C++. Rather, they depend on the particular hardware your machine uses. Furthermore, floating-point numbers are merely an approximation based on the IEEE standard. On most machines, type float occupies 32 bits, of which 1 bit will be used for the sign representation, 8 bits for exponent, and the remaining 23 bits for the mantissa. Because a mantissa always has the form 1.nnnn... the leading 1 can be dropped so there are actually 24 bits allocated for the mantissa. 24-bit accuracy can have a deviation of %0.0000062 from the original number. For higher accuracy, you can use the type double, which provides 53 bits of mantissa. This is more accurate than 24 bits, but you can never get absolute accuracy with bits.
FRIENDSHIP AND NESTED CLASSESWhen you declare a nested class as a friend of its containing class, place the friend declaration after the declaration of the nested class, not before it: class A If you place the friend declaration before the nested class's declaration, the compiler will discard it since the friend class has not been seen yet.
GETTING A PROGRAM'S NAMEThe name of the program's executable file is stored in argv[0] as a null-terminated char array. To access it, declare main() as follows: int main(int argc, char* argv[]) Even if the application were called without command-line arguments, C++ ensures that at least one argument, namely argv[0], is passed to main(). You can extract it like this: int main(int argc, char ** argv)
GUIDELINES FOR HANDHELD COMPUTER PROGRAMMERSHandheld devices and embedded systems revive programming techniques that existed in the olden days, when every byte of memory and every CPU cycle counted. The following guidelines can be useful for programmers who aim at the handheld devices market niche. Be prepared to resolve chronic out of memory exceptions: Handheld
devices do not have a hard disk. Therefore, memory swapping is not an
option. Your program should be prepared to handle recurrent memory
exhaustion exceptions. To minimize the risks of memory exhaustion,
remember to release allocated memory as soon as it's not needed
anymore.
GUIDELINES FOR PORTING CODEPorting software to a different compiler or platform can be very easy or very difficult, depending on several factors. The most important one is the extent to which the software relies on nonportable and compiler-specific features. For example, if your code uses nonstandard keywords such as __closure (in Borland's C++ Builder) and __int64, you will have to replace them with the equivalent keywords supported by the target compiler. Using standard constructs that have the same semantics instead of proprietary keywords is even better. Porting is more difficult if your app is based on a proprietary framework (such as MFC) that the target platform (such as UNIX) doesn't support. In that case, you need to perform code analysis and separate portable code components such as business logic classes, financial and arithmetic functions, and standard functions and classes from nonportable GUI and system APIs. The portable components can be used as is on the target platform. As for the nonportable components, it's likely that your target platform offers a corresponding set of classes or API functions that you can use instead. For example, instead of the spawn() family of functions used in Win32, you can use the fork() and exec() functions in UNIX and Linux. Sometimes, however, you will have to redesign parts of your app from scratch.
ABSOLUTE VALUESThe standard functions abs() and labs() (declared in < cstdlib >) return the absolute value of int and long, respectively. You can use these functions in computations that require absolute values. For example: int begin=0;
ALL ABOUT MAIN()A program must contain a global function called main, which is the designated start of the program. It's illegal to overload main(). However, an implementation must define two versions thereof: int main() The return type of main() shall be int. Note that the use of void as main's return value is noncompliant (although many compilers still permit it). In certain implementations, main() takes additional parameters. The additional parameters should appear after the two mandatory parameters. For example: /* POSIX systems such as Linux and Unix use this form too*/ Declaring main() inline or static is ill-formed. Remember that main is not a keyword. Therefore, you may declare variables, member functions, and constants called main.
ASSIGNING INT TO AN ENUM VARIABLEC++ doesn't allow you to assign integer value to an enumeration directly: enum Direction (Up, Down}; You need to use static_cast to explicitly cast an integer to an enumeration: dir=static_cast < Direction > (0); /*dir equals 'Up'*/ If the int value is outside the valid ranges of the enumeration, the results are undefined: dir=static_cast < Direction > (5);/*undefined behavior*/ Therefore, use this technique with caution.
BLOCKS AND COMPOUND STATEMENTSA "compound statement" (called a "block") is a sequence of one or more statements enclosed between { and }. For example: for (int n=0; n<100; ++n)
CASTING POINTERS TO MEMBERSC++ allows casting a pointer to member to another pointer to member, as long as the signatures of the two match. However, the results of such a cast are implementation-defined. You have to consult your compiler's manual to make sure that such casts are supported. If your code is to be ported to several target platforms, you shouldn't cast pointers to members at all.
CHECKING A STREAM'S STATEThe iostream family provides the following member functions and operators for checking a stream's state: bool good() /*true if no error flag is set*/ good() takes all the stream's flags into account, including eofbit, whereas fail(), operator!(), and operator void*() ignore eofbit. If you want to check whether the stream is in a state of error, use fail(), operator!(), or operator void*(). To check a failure of input operations, call good().
CREATING A TEMPORARY FILEThe standard function tmpfile() is declared in <csdtio> (formerly <stdio.h>) as follows: FILE * tmpfile(); It opens a temporary file for read/write operations and returns a pointer to that file. tmpfile() chooses unique filenames to avoid conflicts with any existing files. The temporary files created by tmpname() are automatically deleted when the program terminates.
DEBUG INFORMATION IN A RELEASE VERSIONIt may be tempting to squeeze out an extra ounce of performance by removing all debug information from an application's release version. Even if an application seems stable at your site, mysterious crashes--due to different settings, incompatible libraries, different device drivers, and so on--may occur when it's shipped to a customer. Therefore, many developers recommend leaving minimal debugging info even in the release version so that the runtime environment can print the call stack in a human-readable format. Without this information, you won't have any information about the cause of a crash.
EMITTING A BEEP` '\a' is the symbolic escape sequence of the BEL character. To emit a beep from your program, write it to the standard output. For example: const char BEEP = '\a';
FACTORIAL FUNCTIONA factorial function is a classic example of recursion. Here's a typical recursive factorial function: int factorial (int num) factorial() calls itself recursively, subtracting 1 from num on each call, until it equals 1. As always, you can use iteration instead of recursion: int factorial (int num) The nonrecursive version is slightly faster because it avoids the overhead of recurrent function calls.
HIDING CUMBERSOME FUNCTION POINTER SYNTAXCan you tell what the following declaration means? void (*p[10]) (void (*)()); p is an "array of 10 pointers to a function returning void and taking a pointer to another function that returns void and takes no arguments," but the cumbersome syntax is nearly indecipherable. You can simplify this declaration considerably by using typedefs. First, declare a typedef for "pointer to a function returning void and taking no arguments" like this: typedef void (*pfv)(); Next, declare another typedef for "pointer to a function returning void and taking a pfv": typedef void (*pf_taking_pfv) (pfv); Now declaring an array of 10 such pointers is a breeze: pf_taking_pfv p[10]; /*equivalent to
OLD-STYLE FUNCTION DECLARATIONSIn earlier stages of C, function declarations looked like this: int func() /*no parameters inside parentheses*/ The old-style function declaration had empty parentheses. The parameters were declared after the parentheses and before the opening brace of the function body. Today, this function would be declared as follows: int func(int a, int b, float f) The old-style function declaration was never supported in C++. However, it was supported in C for many years, until not long ago. Today, the recently approved C99 standard disallows old-style function declarations even in C. Legacy C code still contain this obsolete style, though. If you're porting legacy C code to C++ or to a C compiler that is C99-compliant, check for old style function declarations and change them.
PRIVATE MEMBER FUNCTIONSPrivate class members represent implementation details that shouldn't be accessible to other classes and users. Your class may declare private member functions as well. For example, suppose you have a multimedia player class whose public member functions play(), zoom() and stop(), call internal member functions, or helper functions, that take care of internal operations such as managing memory buffers, threads, and interfaces to sound and video cards. These internal operations are highly specialized and access low-level hardware interfaces. As such, they are likely to change with each new release or port to another platform. Therefore, they are declared private so that other clients don't use them directly.
RETURNING A VALUE FROM A FUNCTION THAT THROWS"Can a function that returns something throw an exception and still return a value?" Let's look at a concrete example: int f() f() has two exit points: the throw statement and the return statement. On each invocation, f() can exit only from one of these exit points. Syntactically, there is no way that f() can both throw an exception and return a value. Thus, the execution path of a return statement and a throw statement are always mutually exclusive. At the logical level, there is another reason why it's impossible to return and throw at the same time. When an exception is thrown, it means that the function encountered an irrecoverable error from which it cannot proceed normally, and therefore it can't return a meaningful value. On the other hand, if the function can return a meaningful result, there's no reason for throwing an exception in the first place.
SIGNATURES OF COPY CONSTRUCTORSFor a class called X, a copy constructor can have one of the following forms: X(X&); Note that the copy constructor's parameter may not be passed by value, nor can you use any other type but a reference. Therefore, the following forms are illegal: X(X); A copy constructor cannot have more parameters, unless they have default values: X(const X&, int n=0); /*OK default value for 2nd parameter*/
STATIC_CAST VERSUS REINTERPRET_CASTThe operators static_cast and reinterpret_cast convert an operand's type. However, they aren't interchangeable; static_cast uses the type information available at compile time to perform the conversion, making the necessary adjustments (such as pointer offset calculation, type promotion) between the source and target types. Its operation, therefore, is relatively safe. On the other hand, reinterpret_cast simply reinterprets the bit pattern of a given object without changing its binary representation. Consider this example: int n=9; double d=static_cast < double > (n); In this example, we convert a variable of type int to double. The binary representation of these types is very different. To convert the int 9 to a double, static_cast needs to properly pad the additional bytes of d. As expected, the result of the conversion is 9.0. reinterpret_cast's behavior is different: int n=9; This time, the results are meaningless. After the cast, d contains a garbage value. This is because reinterpret_cast simply copied the bit pattern of n into d as is, without making the necessary adjustments. For this reason, you should use reinterpret_cast sparingly and judiciously.
STORING DYNAMICALLY ALLOCATED OBJECTS IN STL CONTAINERSSuppose you need to store objects of different types in the same container. Usually you do this by storing pointers to dynamically allocated objects. Instead of using named pointers, insert the elements to the container as follows: class Base {}; vector < Base * > v; This way you ensure that the stored objects can be accessed only through their container. You delete the allocated objects as follows: delete v[0];
STREAMS AND POINTERSLook at the following program. It compiles under every standard-conforming compiler. At first, this may seem surprising because it's impossible to delete an object that is not a pointer: #include < iostream > Why doesn't the delete statement cause a compilation error? After all, i is not a pointer. The reason is that stream objects have a void* conversion operator that is called implicitly in a context requiring a pointer. This conversion enables you to treat streams as file pointers, similarly to the way you treat FILE* variables in C. The compiler invokes the void* conversion operator in the delete statement. Needless to say, this results in undefined behavior at runtime. There are two lessons from this. First, beware of accidentally placing stream objects in a context where true pointers are required; the compiler's type-safety mechanisms are turned off in this case and won't protect you from potential bugs such as this one. Second, notice how dangerous conversion operators can be; use them cautiously.
UNINITIALIZED ENUM VARIABLESAn automatic uninitialized enum variable has an indeterminate value. Uninitialized global and static enum variables have a zero value by default. This means that such an enum variable might have a value that is not in the range of its enumerators. In the following example, the global enum variable st is default initialized to zero. This bug leads to unpredictable behavior at runtime: enum Stat {good=1, bad=2}; /*no enumerator equals 0*/ It's best always to initialize enum variables explicitly, thereby avoiding such bugs: Stat st=good;
VARIABLE LENGTH ARRAYSI n C89 and C++, an array's size must be an integer constant expression so that it can be computed at compile time. In C99, the newly approved ANSI/ISO C standard, this rule was relaxed -- you can now use any integer expression to define an array's size. For example:int func(int dim) The number of elements in arr may change every time func() is called. Therefore, its size can only be computed at runtime. Such an array is called a variable length array: int main() To support variable length arrays, C99 changed the semantics of the sizeof operator. Usually, sizeof expressions are evaluated at compile time. However, when sizeof is applied to a variable length array, the result is evaluated at runtime. Remember that variable length arrays aren't dynamic arrays -- they can't change their size during their lifetime. They differ from ordinary arrays in that they may have a different size every time they are instantiated. Variable length arrays can only be local. Note that standard C++ doesn't support variable length arrays. However, because many C++ compilers are also C compilers, some of them may support this feature as a nonstandard extension.
|
Just Check out some of our sponsors |
|
COPYRIGHT 1998 - 2009 All names used are Trademarks of the respective companies Send mail to
CompanyWebmaster with
questions or comments about this web site.
|