1. Trang chủ
  2. » Công Nghệ Thông Tin

ANSI/ISO C++ Professional Programmer''''s Handbook phần 9 pot

33 381 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 33
Dung lượng 96,09 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 1

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 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 2

delete [] 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 3

The 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 4

const 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 5

Prefer 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 6

As 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 7

readability 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 8

Key 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 9

const 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 10

int *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 11

class Derived1 : public Base { /* */};

class Derived2 : public Base { /* */};

// Base::countCalls(), Derived1::countCalls() and Derived2::countCalls

// hold a shared copy of cnt

Trang 12

int 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 13

The 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 14

ANSI/ISO C++ Professional Programmer's

Handbook

Contents

12 Optimizing Your Code

Trang 15

Function 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 16

Before 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

Ngày đăng: 05/08/2014, 10:20

TỪ KHÓA LIÊN QUAN