|
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: 08 November 2007 18:23:49 -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 > ::i |