Put differently, the statement delete[] p; //free the allocated buffer throw; //re-throw the exception of C's constructor void func //use a pre allocated char array to construct //an o
Trang 1Put differently, the statement
delete[] p; //free the allocated buffer
throw; //re-throw the exception of C's constructor
void func() //use a pre allocated char array to construct
//an object of a different type
{
char * pc = new char[sizeof(Employee)];
Employee *pemp = new (pc) Employee; //construct on char array
// use pemp
pemp->Employee::~Employee(); //explicit destruction
Trang 2delete [] pc;
}
It might be tempting to use a buffer that is allocated on the stack to avoid the hassle of deleting it later:
char pbuff [sizeof(Employee)];
Employee *p = new (pbuff ) Employee; //undefined behavior
However, char arrays of automatic storage type are not guaranteed to meet the necessary alignment requirements ofobjects of other types Therefore, constructing an object of a preallocated buffer of automatic storage type can result
in undefined behavior Furthermore, creating a new object at a storage location that was previously occupied by aconst object with static or automatic storage type also results in undefined behavior For example
const Employee emp;
void bad_placement() //attempting to construct a new object
//at the storage location of a const object
The size of a class or a struct might be larger than the result of adding the size of each data member in it This is
because the compiler is allowed to add additional padding bytes between members whose size does not fit exactly into
a machine word (see also Chapter 13) For example
Person person = {{"john"}, 30, {"lippman"}};
memset(&person, 0, 5+4+8 ); //may not erase the contents of
//person properly
}
On a 32-bit architecture, three additional bytes can be inserted between the first and the second members of Person,increasing the size of Person from 17 bytes to 20
On some implementations, the memset() call does not clear the last three bytes of the member lastName
Therefore, use the sizeof operator to calculate the correct size:
memset(&p, 0, sizeof(Person));
Trang 3The Size Of A Complete Object Can Never Be Zero
An empty class doesn't have any data members or member functions Therefore, the size of an instance is seeminglyzero However, C++ guarantees that the size of a complete object is never zero Consider the following example:
class Empty {};
Empty e; // e occupies at least 1 byte of memory
If an object is allowed to occupy zero bytes of storage, its address can overlap with the address of a different object.The most obvious case is an array of empty objects whose elements all have an identical address To guarantee that acomplete object always has a distinct memory address, a complete object occupies at least one byte of memory.Non-complete objects for example, base class subobjects in a derived class can occupy zero bytes of memory
User-Defined Versions of new and delete Cannot Be
Declared in a Namespace
User-defined versions of new and delete can be declared in a class scope However, it is illegal to declare them in
a namespace To see why, consider the following example:
char *pc;
namespace A
{
void* operator new ( size_t );
void operator delete ( void * );
void f() { delete pc; } // A::delete or ::delete?
Declaring new and delete in namespace A is confusing for both compilers and human readers Some programmersmight expect the operator A::delete to be selected in the function f() because it matches the operator new thatwas used to allocate the storage In contrast, others might expect delete to be called because A::delete is notvisible in f() For this reason, the Standardization committee decided to disallow declarations of new and delete
in a namespace
Overloading new and delete in a Class
It is possible to override new and delete and define a specialized form for them for a given class Thus, for a class
C that defines these operators, the following statements
C* p = new C;
delete p;
invoke the class's versions of new and delete, respectively Defining class-specific versions of new and delete
is useful when the default memory management scheme is unsuitable This technique is also used in applications thathave a custom memory pool In the following example, operator new for class C is redefined to alter the defaultbehavior in case of an allocation failure; instead of throwing std::bad_alloc, this specific version throws a
Trang 4const char * A matching operator delete is redefined accordingly:
#include <cstdlib> // malloc() and free()
void* operator new (size_t size); //implicitly declared static
void operator delete (void *p); //implicitly declared static
Guidelines for Effective Memory Usage
Choosing the correct type of storage for an object is a critical implementation decision because each type of storagehas different implications for the program's performance, reliability, and maintenance This section tells you how tochoose the correct type of storage for an object and thus avoid common pitfalls and performance penalties Thissection also discusses general topics that are associated with the memory model of C++, and it compares C++ to otherlanguages
Trang 5Prefer Automatic Storage to Free Store Whenever Possible
Creating objects on the free store, when compared to automatic storage, is more expensive in terms of performancefor several reasons:
Runtime overhead Allocating memory from the free store involves negotiations with the operating system.
When the free store is fragmented, finding a contiguous block of memory can take even longer In addition, theexception handling support in the case of allocation failures adds additional runtime overhead
●
Maintenance Dynamic allocation might fail; additional code is required to handle such exceptions.
●
Safety An object might be accidentally deleted more than once, or it might not be deleted at all Both of these
are a fertile source of bugs and runtime crashes in many applications
Such bugs are quite common in large programs that frequently allocate objects on the free store Often, it is possible
to create objects on the stack, thereby simplifying the structure of the program and eliminating the potential for suchbugs Consider how the use of a local string object simplifies the preceding code sample:
Trang 6As a rule, automatic and static storage types are always preferable to free store.
Correct Syntax for Local Object Instantiation
The correct syntax for instantiating a local object by invoking its default constructor is
string str; //no parentheses
Although empty parentheses can be used after the class name, as in
string str(); //entirely different meaning
the statement has an entirely different meaning It is parsed as a declaration of a function named str, which takes noarguments and returns a string by value
Zero As A Universal Initializer
The literal 0 is an int However, it can be used as a universal initializer for every fundamental data type Zero is aspecial case in this respect because the compiler examines its context to determine its type For example:
void *p = 0; //zero is implicitly converted to void *
float salary = 0; // 0 is cast to a float
char name[10] = {0}; // 0 cast to a '\0'
bool b = 0; // 0 cast to false
void (*pf)(int) = 0; // pointer to a function
int (C::*pm) () = 0; //pointer to a class member
Always Initialize Pointers
An uninitialized pointer has an indeterminate value Such a pointer is often called a wild pointer It is almost
impossible to test whether a wild pointer is valid, especially if it is passed as an argument to a function (which in turncan only verify that it is not NULL) For example
void func(char *p );
int main()
{
char * p; //dangerous: uninitialized
// many lines of code; p left uninitialized by mistake
if (p)//erroneously assuming that a non-null value indicates a valid address {
func(p); // func has no way of knowing whether p has a valid address }
return 0;
}
Even if your compiler does initialize pointers automatically, it is best to initialize them explicitly to ensure code
Trang 7readability and portability.
Explicit Initializations of POD Object
As was previously noted, POD objects with automatic storage have an indeterminate value by default in order toavoid the performance penalty incurred by initialization However, you can initialize automatic POD objects
explicitly when necessary The following sections explain how this is done
Initializing Local Automatic Structs and Arrays
One way to initialize automatic POD objects is by calling memset() or a similar initialization function However,there is a much simpler way to do it without calling a function, as you can see in the following example:
Person person ={0}; //ensures that all members of
//person are initialized to binary zeros
applicable to local automatic arrays of fundamental types as well as to arrays of POD objects :
void f()
{
char name[100] = {0}; //all array elements are initialized to '\0'
float farr[100] = {0}; //all array elements are initialized to 0.0
int iarr[100] = {0}; //all array elements are initialized to 0
void *pvarr[100] = {0};//array of void * all elements are initialized to NULL // use the arrays
Trang 8Key key = {5}; // first member of Key is of type int
// any additional bytes initialized to binary zeros
}
Detecting a Machine's Endian
The term endian refers to the way in which a computer architecture stores the bytes of a multibyte number in
memory When bytes at lower addresses have lower significance (as is the case with Intel microprocessors, for
instance), it is called little endian ordering Conversely, big endian ordering describes a computer architecture in
which the most significant byte has the lowest memory address The following program detects the endian of themachine on which it is executed:
int main()
{
union probe
{
unsigned int num;
unsigned char bytes[sizeof(unsigned int)];
};
probe p = { 1U }; //initialize first member of p with unsigned 1
bool little_endian = (p.bytes[0] == 1U); //in a big endian architecture, //p.bytes[0] equals 0
return 0;
}
The Lifetime Of A Bound Temporary Object
You can safely bind a reference to a temporary object The temporary object to which the reference is bound persistsfor the lifetime of the reference For example
class C
{
Trang 9const C& cr = C(2); //bind a reference to a temp; temp's destruction
//deferred to the end of the program
C c2 = cr; //use the bound reference safely
int val = cr.getVal();
return 0;
}//temporary destroyed here along with its bound reference
Deleting A Pointer More Than Once
The result of applying delete to the same pointer after it has been deleted is undefined Clearly, this bug shouldnever happen However, it can be prevented by assigning a NULL value to a pointer right after it has been deleted It isguaranteed that a NULL pointer deletion is harmless For example
// many lines of code
delete ps; // ps is deleted for the second time Harmless however
}
Data Pointers Versus Function Pointers
Both C and C++ make a clear-cut distinction between two types of pointers data pointers and function pointers Afunction pointer embodies several constituents, such as the function's signature and return value A data pointer, onthe other hand, merely holds the address of the first memory byte of a variable The substantial difference between thetwo led the C standardization committee to prohibit the use of void* to represent function pointers, and vice versa
In C++, this restriction was relaxed, but the results of coercing a function pointer to a void* are
implementation-defined The opposite that is, converting data pointers to function pointers is illegal
Pointer Equality
Pointers to objects or functions of the same type are considered equal in three cases:
If both pointers are NULL For example
●
Trang 10int *p1 = NULL, p2 = NULL;
bool equal = (p1==p2); //true
If they point to the same object For example
●
char c;
char * pc1 = &c;
char * pc2 = &c;
bool equal = (pc1 == pc2); // true
If they point one position past the end of the same array For example
●
int num[2];
int * p1 = num+2, *p2 = num+2;
bool equal = ( p1 == p2); //true
Storage Reallocation
In addition to malloc() and free(), C also provides the function realloc() for changing the size of an
existing buffer C++ does not have a corresponding reallocation operator Adding operator renew to C++ was one ofthe suggestions for language extension that was most frequently sent to the standardization committee Instead, thereare two ways to readjust the size of memory that is allocated on the free store The first is very inelegant and errorprone It consists of allocating a new buffer with an appropriate size, copying the contents of the original buffer to itand, finally, deleting the original buffer For example
void reallocate
{
char * p new char [100];
// fill p
char p2 = new char [200]; //allocate a larger buffer
for (int i = 0; i<100; i++) p2[i] = p[i]; //copy
delete [] p; //release original buffer
}
Obviously, this technique is inefficient and tedious For objects that change their size frequently, this is unacceptable.The preferable method is to use the container classes of the Standard Template Library (STL) STL containers arediscussed in Chapter 10, "STL and Generic Programming."
Local Static Variables
By default, local static variables (not to be confused with static class members) are initialized to binary zeros
Conceptually, they are created before the program's outset and destroyed after the program's termination However,like local variables, they are accessible only from within the scope in which they are declared These properties makestatic variables useful for storing a function's state on recurrent invocations because they retain their values from theprevious call For example
void MoveTo(int OffsetFromCurrentX, int OffsetFromCurrentY)
{
static int currX, currY; //zero initialized
Trang 11class Derived1 : public Base { /* */};
class Derived2 : public Base { /* */};
// Base::countCalls(), Derived1::countCalls() and Derived2::countCalls
// hold a shared copy of cnt
Trang 12int d1Calls = d1.countCalls(); //d1Calls = 1
int d2Calls = d2.countCalls(); //d2Calls also = 1
return 0;
}
Static variables are problematic in a multithreaded environment because they are shared and have to be accessed bymeans of a lock
Global Anonymous Unions
An anonymous union (anonymous unions are discussed in Chapter 12, "Optimizing Your Code") that is declared in a
named namespace or in the global namespace has to be explicitly declared static For example
static union //anonymous union in global namespace
Trang 13The const and volatile Properties of an Object
There are several phases that comprise the construction of an object, including the construction of its base and
embedded objects, the assignment of a this pointer, the creation of the virtual table, and the invocation of the
constructor's body The construction of a cv-qualified (const or volatile) object has an additional phase, which
turns it into a const/volatile object The cv qualities are effected after the object has been fully constructed
Many object-oriented programming languages have a built-in garbage collector, which is an automatic memory
manager that detects unreferenced objects and reclaims their storage (see also Chapter 14, "Concluding Remarks andFuture Directions," for a discussion on garbage collection) The reclaimed storage can then be used to create newobjects, thereby freeing the programmer from having to explicitly release dynamically-allocated memory Having anautomatic garbage collector is handy because it eliminates a large source of bugs, runtime crashes, and memory leaks.However, garbage collection is not a panacea It incurs additional runtime overhead due to repeated compaction,reference counting, and memory initialization operations, which are unacceptable in time-critical applications
Furthermore, when garbage collection is used, destructors are not necessarily invoked immediately when the lifetime
of an object ends, but at an indeterminate time afterward (when the garbage collector is sporadically invoked) Forthese reasons, C++ does not provide a garbage collector Nonetheless, there are techniques to minimize and eveneliminate the perils and drudgery of manual memory management without the associated disadvantages of garbagecollection The easiest way to ensure automatic memory allocation and deallocation is to use automatic storage Forobjects that have to grow and shrink dynamically, you can use STL containers that automatically and optimally adjusttheir size Finally, in order to create an object that exists throughout the execution of a program, you can declare itstatic Nonetheless, dynamic memory allocation is sometimes unavoidable In such cases, auto_ptr(discussed
in Chapters 6 and 11, "Memory Management") simplifies the usage of dynamic memory
Effective and bug-free usage of the diversity of C++ memory handling constructs and concepts requires a high level
of expertise and experience It isn't an exaggeration to say that most of the bugs in C/C++ programs are related tomemory management However, this diversity also renders C++ a multipurpose, no compromise programming
language
Contents
© Copyright 1999, Macmillan Computer Publishing All rights reserved
Trang 14ANSI/ISO C++ Professional Programmer's
Handbook
Contents
12 Optimizing Your Code
Trang 15Function Objects Versus Function Pointers
exponentially every 18 months or so However, in many other application domains, a hardware upgrade is less
favorable because it is too expensive or because it simply is not an option In proprietary embedded systems with128K of RAM or less, extending the RAM requires redesigning the entire system from scratch, as well as investingseveral years in the development and testing of the new chips In this case, code optimization is the only viable choicefor satisfactory performance
But optimization is not confined to esoteric application domains such as embedded systems or hard core real-timeapplications Even in mainstream application domains such as financial and billing systems, code optimization issometimes necessary For a bank that owns a $1,500,000 mainframe computer, buying a faster machine is less
preferable than rewriting a few thousand lines of critical code Code optimization is also the primary tool for
achieving satisfactory performance from server applications that support numerous users, such as Relational DatabaseManagement Systems and Web servers
Another common belief is that code optimization implies less readable and harder to maintain software This is notnecessarily true Sometimes, simple code modifications such as relocating the declarations in a source file or choosing
a different container type can make all the difference in the world Yet none of these changes entails unreadable code,nor do they incur any additional maintenance overhead In fact, some of the optimization techniques can even
improve the software's extensibility and readability More aggressive optimizations can range from using a simplifiedclass hierarchy, through the combination of inline assembly code The result in this case is less readable, harder tomaintain, and less portable code Optimization can be viewed as a continuum; the extent to which it is applied
depends on a variety of considerations
Scope of This Chapter
Optimization is a vast subject that can easily fill a few thick volumes This chapter discusses various optimizationtechniques, most of which can be easily applied in C++ code without requiring a deep understanding of the
underlying hardware architecture of a particular platform The intent is to give you a rough estimate of the
performance cost of choosing one programming strategy over another (you can experiment with the programs that arediscussed in the following sections on your computer) The purpose is to provide you with practical guidelines andnotions, rather than delve into theoretical aspects of performance analysis, efficiency of algorithms, or the Big Ohnotation
Trang 16Before Optimizing Your Software
Detecting the bottlenecks of a program is the first step in optimizing it It is important, however, to profile the releaseversion rather than the debug version of the program because the debug version of the executable contains additionalcode A debug-enabled executable can be about 40% larger than the equivalent release executable The extra code isrequired for symbol lookup and other debug "scaffolding" Most implementations provide distinct debug and releaseversions of operator new and other library functions Usually, the debug version of new initializes the allocatedmemory with a unique value and adds a header at block start; the release version of new doesn't perform either ofthese tasks Furthermore, a release version of an executable might have been optimized already in several ways,including the elimination of unnecessary temporary objects, loop unrolling (see the sidebar "A Few Compiler
Tricks"), moving objects to the registers, and inlining For these reasons, you cannot assuredly deduce from a debugversion where the performance bottlenecks are actually located
A Few Compiler Tricks
A compiler can automatically optimize the code in several ways The named return value and loop
unrolling are two instances of such automatic optimizations.
Consider the following code:
int *buff = new int[3];
for (int i =0; i<3; i++)
buff[i] = 0;
This loop is inefficient: On every iteration, it assigns a value to the next array element However,
precious CPU time is also wasted on testing and incrementing the counter's value and performing a jumpstatement To avoid this overhead, the compiler can unroll the loop into a sequence of three assignment
statements, as follows:
buff[0] = 0;
buff[1] = 0;
buff[2] = 0;
The named return value is a C++-specific optimization that eliminates the construction and destruction of
a temporary object When a temporary object is copied to another object using a copy constructor, and
when both these objects are cv-unqualified, the Standard allows the implementation to treat the two
objects as one, and not perform a copy at all For example