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

Core C++ A Software Engineering Approach phần 6 ppsx

120 322 0

Đ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

Tiêu đề Core C++ A Software Engineering Approach Part 6
Trường học Unknown
Chuyên ngành Software Engineering
Thể loại Lecture notes
Năm xuất bản Unknown
Thành phố Unknown
Định dạng
Số trang 120
Dung lượng 2,1 MB

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

Nội dung

using the addition operator for text concatenation.Let us consider a String class with two data members: a pointer to a dynamically allocated character array and the integer with the max

Trang 1

However, the use of overloaded operators is not uncommon They are especially popular in C++ libraries, including the Standard Template Library (STL), and you have to understand what they do and how they are implemented

The comparison that I make between member functions and friend functions is very important All too often we make design decisions on the basis of hearsay or arbitrary biases rather than from the point of view of goals of object-oriented programming

Make sure that you do not treat friend functions as X-rated material Use them if they provide more flexibility for better implementation But do not overuse them

Chapter 11 Constructors and Destructors: Potential Trouble

Topics in this Chapter

ϒΠ More on Passing Objects by Value

ϒΠ Operator Overloading for Nonnumeric Classes

ϒΠ More on the Copy Construction

ϒΠ Overloading the Assignment Operator

ϒΠ Practical Considerations: What to Implement

ϒΠ Summary

Overloaded operator functions give a new twist to object-oriented programming Instead of

concentrating on binding together data and operations and related ideas, we find ourselves busy with aesthetic considerations and the issues of equally treating built-in types and programmer-

defined types by a C++ program

This chapter is a direct continuation of the previous chapter In Chapter 10,"Operator Functions: Another Good Idea," I discussed the issues that are related to the design of numeric classes, such as classes Complex and Rational. Objects of these classes are object instances in their own right All

of the issues related to dealing with objects apply to them: class declaration, control of access to class members, design of member functions, object definition, object initialization, and messages

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 2

programmer-defined types; it handles objects of programmer-defined types It is object instances,

or variables, that are multiplied, compared, and so on I make this point because I think that you have learned enough about classes and objects to be sensitive to the loose language in object-

oriented discussions and to avoid it if possible

In other words, objects of programmer-defined numeric classes can be handled by the client code similar to variables of built-in types This is why it makes perfect sense to support operator

overloading for them The C++ principle of treating the instances of built-in types and defined classes equally works well for these classes In this chapter, I am going to discuss

programmer-overloaded operators for classes whose objects cannot be added, multiplied, subtracted, or divided For example, class String can be designed to manage text in memory Because of the nonnumeric nature of such classes, overloaded operator functions for these classes look artificial For example, you can implement String concatenation using the overloaded addition operator or String

comparison using the overloaded equality operator But you would be hard-pressed to come up with a reasonable interpretation of multiplication or division for String objects Nevertheless, overloaded operator functions for nonnumeric classes are popular, and you should know how to deal with them

The important distinction of these nonnumeric classes is the variable amount of data that objects of the same class can use The objects of numeric classes always use the same amount of memory In class Rational, for example, there are always two data members, one for the numerator, another for the denominator

In class String, however, the amount of text that is stored in one object might be different from the amount of text stored with another object If the class reserves for each object the same (large enough) amount of memory, the program has to deal with two unpleasant extremes¡Xthe waste of memory (when the actual amount of text is less than the reserved amount of memory) and memory

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 3

overflow (when the object has to store too much text) These two dangers always haunt the

designers of classes that allocate the same amount of memory to each object

C++ resolves this problem by allocating a fixed amount of memory to each object (either to the heap or to the stack) according to the class description and then allocating additional memory to the heap as required This additional amount of heap memory changes from one object to another It might even change for an object during its lifetime For example, a String object might receive additional heap memory to accommodate text that is concatenated to the text currently in the object

Dynamic management of heap memory entails the use of constructors and destructors Their

unskilled use might negatively affect program performance What is worse is that their use might result in corruption of memory and loss of program integrity that is not known in any other

language but C++ Every C++ programmer should be aware of these dangers This is why I

included these issues in the title of the chapter even though this chapter continues the discussion of overloaded operator functions

For simplicity of discussion, I will introduce necessary concepts for the fixed-sized class Rational

that you saw in Chapter 10 In this chapter I will apply these concepts to class String with

dynamic management of heap memory As a result, you will hone your programming intuition about relationships between object instances in the client code You will see that the relationships between objects are different from relationships between variables of built-in types, despite the effort to treat them equally In other words, you are in for a big surprise

Make sure you do not skip the material in this chapter The dangers related to the roles of

constructors and destructors in C++ are real, and you should know how to protect yourself, your boss, and the users of your code

More on Passing Objects by Value

Earlier, in Chapter 7, "Programming with C++ Functions," I argued against passing objects to

functions as value parameters or as pointer parameters and promoted passing parameters by

reference instead

I explained that pass by reference is almost as simple as pass by value, but it is faster¡Xfor input parameters that are not modified by the function Pass by reference is as fast as pass by pointer, but its syntax is much simpler¡Xfor output parameters that are modified by the function in the course of its execution

I also noticed that the syntax for pass by reference is exactly the same for both input and output function parameters This is why I suggested that you use the const modifier for input parameters, indicating that the parameter does not change as the result of function execution When you use no

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 4

modifiers, this should indicate that the parameter changes during function execution.

I also argued against returning object values from functions unless it is necessary for sending other messages to the returned object (chain syntax in expressions)

With this approach, the pass by value should be limited to passing built-in types as input

parameters to functions and returning values of built-in types from functions Why is this

acceptable for input values of built-in types? Passing them by pointer will add complexity and could mislead the reader into believing that the parameter changes within the function Passing them by reference (with the const modifier) is not very difficult, but it adds a little bit of

complexity Since they are small, passing them by reference has no performance advantages This

is why the simplest way of passing parameters is appropriate for built-in types

In the last chapter, you learned enough programming techniques to be able not only to discuss advantages and disadvantages of different modes of passing parameters but also to see the actual sequence of invocations

Also on several occasions, I told you that initialization and assignment, even though they both use the equal sign, are treated differently In this section, I will use debugging code to demonstrate the differences

I will demonstrate both issues using the program in Listing 11.1, which contains a simplified (and modified) class Rational from the last chapter with its test driver

Of all Rational functions, I left only normalize(), show(), and operator+(). Notice that the overloaded operator function operator+() is not a member function of class Rational; it is a friend This is why I was careful to say at the beginning of this paragraph, "of all Rational

functions," not "of all Rational member functions." I do this because I want to stress that a friend function is, for all intents and purposes, a class member function It is implemented in the same file

as are other member functions, it has the same access rights to class private members as do other member functions, and it is useless for working with objects of any class other than class

Rational. It is only the invocation syntax that makes it different from member functions, but for overloaded operators, the operator syntax is the same for member functions and friend functions

Example 11.1 Example of passing object parameters by value.

#include <iostream.h>

class Rational {

long nmr, dnm; // private data

void normalize(); // private member function

public:

Rational(long n=0, long d=1) // general, conversion, default

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 5

{ nmr = n; dnm = d;

this->normalize();

cout << " created: " << nmr << " " << dnm << endl; }

Rational(const Rational &r) // copy constructor

{ nmr = r.nmr; dnm = r.dnm;

cout << " copied: " << nmr << " " << dnm << endl; }

void operator = (const Rational &r) // assignment operator

{ nmr = r.nmr; dnm = r.dnm;

cout << " assigned: " << nmr << " " << dnm << endl; }

~Rational() // destructor

{ cout << " destroyed: " << nmr << " " << dnm << endl; }

friend Rational operator + (const Rational x, const Rational y);

void show() const;

} ; // end of class specification

void Rational::show() const

long gcd = nmr, value = dnm; // greatest common divisor

while (value != gcd) { // stop when the GCD is found

if (gcd > value)

gcd = gcd - value; // subtract smaller from greater

else value = value - gcd; }

nmr = sign * (nmr/gcd); dnm = dnm/gcd; } // make dnm positive

Rational operator + (const Rational x, const Rational y)

{ return Rational(y.nmr*x.dnm + x.nmr*y.dnm, y.dnm*x.dnm); }

int main()

{ Rational a(1,4), b(3,2), c;

cout << endl;

c = a + b;

a.show(); cout << " +"; b.show(); cout << " ="; c.show();

cout << endl << endl;

Rational::Rational(long n=0, long d=1) // default values

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 6

example, when passing parameters by value to the operator+() function or when returning a

Rational object from this function

Rational::Rational(const Rational &r) // copy constructor

{ nmr = r.nmr; dnm = r.dnm; // copy data members

cout << " copied: " << nmr << " " << dnm << endl; }

This constructor is called when Rational arguments are passed by value to the friend operator function operator+(). Despite appearances, the copy constructor is not called when operator+()

returns the object value, since the general constructor with two arguments is called prior to

returning from the operator+() function

The destructor does not have a meaningful job in the Rational class, and I added it only for the sake of the debugging statement that fires when a Rational object is destroyed

The most interesting function here is an overloaded assignment operator function Its job is to copy the data members of one Rational object into the data members of another Rational object How

is its duty different from that of the copy constructor? The answer is that there is no difference, at least at this stage The return type is different¡Xthe copy constructor must not have the return type, and the assignment operator, as most member functions, must have a return type For simplicity, I return void.

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 7

operator is always written between the first and the second operand When adding two operands, write the first operand, the operator, and the second operand (e.g., a + b). When using the

assignment, write the first operand, the operator, and the second operand (e.g., a = b). In the function call syntax, object a is the target of the message: In the assignment operator function

above, nmr and dnm belong to the target object a. The object b is the argument of this function call:

In the assignment operator function above, r.nmr and r.dnm belong to the actual argument b.

Hence the function call syntax for the assignment operator is a.operator= (b).

Because this operator returns void, it cannot support chain assignments in the client code, for example, a = b = c. This expression is interpreted by the compiler as a= (b = c). This means that the return value of the assignment b = c (or b.operator=(c)) is used as a parameter in the assignment a.operator=(b.operator=(c)). For this expression to be valid, the assignment operator should return the value of the class type (here, Rational), Since the assignment operator was designed so that it returns void, the chain expression will be labeled by the compiler as a syntax error For our first look at the assignment operator, this is not important The chain

assignment will be used later in the chapter

The output of the program in Listing 11.1 is shown in Figure 11-1 The first three messages

"created" come from creation and initialization of three Rational objects in main(). The two

"copied" messages come from the data flow to the overloaded operator function operator+().

The next message "created" comes from the call to the Rational constructor in the body of the function operator+().

Figure 11-1 Output for program in Listing 11.1

All these calls to constructors take place at the beginning of the function execution Next comes a series of events that takes place when the execution reaches the closing brace of the function body and local and temporary objects are destroyed The first two "destroyed" messages occur when two local copies of actual arguments (3/2 and 1/4) are destroyed, and the destructor is called for these

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 8

two objects The object that contains the sum of parameters cannot be destroyed before it is used in the assignment operator The next message "assigned" comes from the call to the overloaded

assignment operator, and the message "destroyed" comes from the destructor for the object that was created in the body of the function operator+(). The last three "destroyed" messages come from the destructors that are called when the execution reaches the closing brace of main(), and objects

a, b, and c are destroyed Since the copy constructor is not called, the message "copied" does not appear in the output

This sequence of events plays differently if two ampersand signs are added in the interface of the

function.) In this case, failure to keep related parts of code consistent is not deadly¡Xthe compiler would alert you that the code has syntax errors

The results of the execution of program in Listing 11.1 with the operator+() above are shown in

Figure 11-2 You see that four function calls are missing: Two parameter objects are not created and two parameter objects are not destroyed

Figure 11-2 Output for program in Listing 11.1 and passing parameters by reference.

Trang 9

applicable)

Next, let me demonstrate the difference between initialization and assignment In Listing 11.1, variable c is assigned in the expression c = a + b How do I know that it is assigned and not initialized? Because there is no type name to the left of c. Its type is defined earlier at the

beginning of main(). In contrast, this version of main() creates and immediately initializes the object c to the sum of a and b rather than creating and assigning to c in separate statements

int main()

{ Rational a(1,4), b(3,2), c = a + b;

a.show(); cout << " +"; b.show(); cout << " ="; c.show();

cout << endl << endl;

return 0; }

Figure 11-3 shows the results of the execution of the program in Listing 11.1 with passing

parameters by reference and this main() function You see that the assignment operator is not called here Neither is the copy constructor¡Xthe natural result of the switch from pass by value to pass by reference

Figure 11-3 Output for program in Listing 11.1 , passing parameters by reference and

using object initialization rather than assignment.

Later, I will use a similar technique to demonstrate the difference between initialization and

assignment for the String class

TIP

Distinguish between object initialization and object assignment At initialization, a constructor is called, and the assignment operator call is bypassed At assignment, the assignment operator is called, and the constructor call is bypassed

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 10

These ideas about avoiding passing object parameters by value and distinguishing between

initialization and assignment are very important Make sure that you are able to read the client code and say, "Here a constructor is called, and here the assignment operator is called." Develop your intuition to enable you to perform this type of analysis

Operator Overloading for Nonnumeric Classes

As I noted in the introduction to this chapter, the extension of built-in operators to numeric classes

is natural Overloaded operator functions for these classes are very similar to built-in operators Misinterpretation of their meaning by the client programmer or by the maintainer is not likely The idea of treating values of built-in types and programmer-defined types equally is a sound one that lends itself to straightforward implementation

Operators can be applied to the objects of nonmathematical classes as well, but the meaning of addition, subtraction, and other operators might be stretched This is similar to the story of icons for command input in the graphical user interface

In the beginning, there was the command line interface, and users had to type long commands with parameters, keys, switches, and so on Then there were menu bars with text entries By selecting the entry, the user was able to enter the command to be executed without having to type the whole command Then there were hot keys: By pressing the hot key combination, the user was able to activate commands directly, without removing the hand from the keyboard and going through

several menus and submenus Then there was the toolbar with command buttons: By clicking the toolbar button, the user was able to activate a command without needing to know the hot key

combinations The icons on the face of these command buttons were unambiguous and intuitively clear: Open, Close, Cut, Print When more and more icons were added, they became less and less intuitive: New, Paste, Output, Execute, Go

To help the user learn the icons, tool tip messages were added The user interface has become more complex; applications require more disk space, memory, and programming efforts; and users are probably no better off now than they used to be with menus and hot keys Similarly, we started with operator overloading for numeric classes, and now we are going to use operator functions for

nonnumerical classes This will require you to learn more rules, to write more code, and deal with more complexity And the client code might be better off using old-fashioned function calls rather than modern overloaded operators

The String Class

I will discuss a popular example of using overloaded operator functions for nonnumeric classes:

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 11

using the addition operator for text concatenation.

Let us consider a String class with two data members: a pointer to a dynamically allocated

character array and the integer with the maximum number of valid characters that can be inserted into the dynamically allocated heap memory Actually, the C++ Standard Library contains class

String (with the first letter in lowercase) that is designed to satisfy most requirements for text manipulation This is a great class to use It is much more powerful than the class that I am going to discuss here, but I cannot use class String for these examples because it is too complex, and

details would take away from the discussion of dynamic memory management and its

consequences

The client code can create objects of the class in two ways, by specifying the maximum number of valid characters and by specifying the text contents of the string Specifying the number of

characters requires one integer parameter Specifying the text contents also requires one parameter,

a character array The types of these parameters are different, so they have to be used in different constructors Since each of these constructors has exactly one parameter of a nonclass type, which they convert to a class value, they are called conversion constructors

The first conversion constructor, with the parameter for the length of the string to allocate, has the default argument value zero If a String object is created using this default value (no parameters are specified), then the length of the text allocated for the object is zero In this case, the first

conversion constructor is used as a default constructor (e.g., String s;).

The second conversion constructor, with the character array as the parameter, does not have the default argument value It would not be difficult to give it the default value of, say, an empty string, but then the compiler would have difficulty interpreting the function call String s¡Xdo I want to call the first constructor with the default value of zero length, or do I want to call the second

constructor with the default value of an empty string?

The current contents of the string can be modified by the client code by calling the member

function modify() that specifies the new text contents of the target object To access the contents

of the String object, the member function show() can be used This function returns the pointer to the heap memory allocated to the object This pointer can be used by the client code to print the contents of the string, compare it with other text, and so on Listing 11.2 shows a program that implements class String.

Example 11.2 Class String with dynamically allocated heap memory.

Trang 12

char *str; // dynamically allocated char array int len;

public:

String (int length=0); // conversion/default constructor String(const char*); // conversion constructor

~String (); // deallocate dynamic memory

void modify(const char*); // change the array contents

char* show() const; // return a pointer to the array } ;

String::String(int length)

{ len = length;

str = new char[len+1]; // default size is 1

if (str==NULL) exit(1); // test for success

str[0] = 0; } // empty String of 0 length is ok

String::String(const char* s)

{ len = strlen(s); // measure length of incoming text str = new char[len+1]; // allocate enough heap space

if (str==NULL) exit(1); // test for success

strcpy(str,s); } // copy text into new heap memory

String::~String()

{ delete str; } // return heap memory (not the pointer!)

void String::modify(const char a[]) // no memory management here

{ strncpy(str,a,len-1); // protect from overflow

str[len-1] = 0; } // terminate String properly

char* String::show() const // not a good practice, but ok

{ return str; }

int main()

{

String u("This is a test.");

String v("Nothing can go wrong.");

cout << " u = " << u.show() << endl; // result is ok

cout << " v = " << v.show() << endl; // result is ok

v.modify("Let us hope for the best."); // input is truncated

cout << " v = " << v.show() << endl;

strcpy(v.show(),"Hi there"); // bad practice

cout << " v = " << v.show() << endl;

return 0;

}

Dynamic Management of Heap Memory

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 13

The first code line of the first conversion constructor sets the value of data member len; the

second code line sets the value of data member str by allocating the required amount of heap

memory Then it tests for success of memory allocation, and puts zero (character '\0') into the beginning of the allocated memory For any C++ library function, this text content appears to be empty, although it has space for the number of characters specified by the client code

If the client code defines a String object and does not provide arguments, this constructor is used

as a default constructor that allocates one character on the heap and sets it to '\0' to indicate the empty string

Figure 11-4 shows the memory diagram for the execution of each statement of the constructor for the following statement

Figure 11-4 The memory diagram for the first conversion constructor in Listing 11.2

Part A shows that after executing the statement len = length, the data member len is initialized

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 14

to 20 (it contains a value) and the pointer str remains noninitialized (it points anywhere it wants) Part B shows that after the executing of the rest of the constructor body, the heap space (21

characters) is allocated, is pointed to by the pointer str, and its first character is set to 0 Drawing

a diagram for a simple object might seem to be overkill, but I recommend drawing these diagrams for all code that manipulates pointers and heap memory This is the best way to develop your

programming intuition for dynamic memory management

The first line of code of the second conversion constructor measures the length of the string

specified by the client code and sets the data member len. The second line sets the data member

str by allocating the required amount of heap memory to be pointed to by str and copies the characters specified by the client code into the allocated memory The library function strcpy()

copies the characters from the argument array and appends the terminating zero

Figure 11-5 shows the steps of the object initialization for the following statement

Figure 11-5 The memory diagram for the second conversion constructor in Listing 11.2

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 15

The third method is not to keep the string length as a data member at all but compute the length on the fly by calling the strlen() function This is an example of the time-space tradeoff The third approach is better if you

do not need the length often and are loathe to allocate an extra integer for each string object

Since heap memory is allocated for each String object individually, many programmers feel that this memory should be viewed as part of the object With this approach, String objects appear to the client code as objects of variable length, depending on the size of the allocated heap memory This view is valid, but it results in more confusing explanations of the workings of constructors and destructors and somewhat blurs the concept of the class itself

I prefer the approach illustrated by the diagrams shown in Figures 11-4 and 11-5 It reflects the C++ principle that the class is a blueprint for object instances This blueprint is the same for all

String objects According to the blueprint, each String object has two data members; the size of each String object is the same When this statement in the client code is executed,

String t(20); // two data members are allocated on the stack

the object t is allocated two data members on the stack; the heap memory is allocated by String

member functions that execute for a particular object Different String objects can have different amounts of heap memory or they can free memory; or acquire more memory without changing their identity

This approach does not change as String objects themselves are allocated on the heap Consider this example of the client code

This approach gives me the convenience of thinking that all objects of the same class are the same size When an object is created, there are two separate processes: creation of the object (always of

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 16

the same size) and a constructor call It initializes the object data members, including pointers that point to the heap memory.

The destructor deletes the memory allocated dynamically on the heap It is called just before the object is going to be destroyed When the object is destroyed, the memory allocated to its data

members str and len is also destroyed and returned for further use If the object was allocated on the stack, as object u and v in main() in Listing 11.2 are, this memory goes back to the stack If the object was allocated on the heap (as the unnamed object pointed to by pointer p), the memory

allocated for len and str goes back to the heap But in all cases, the memory that the destructor deletes (pointed to by pointer str) goes back to the heap before the data members len and str

disappear Otherwise the destructor statement delete str; would be illegal

Function modify() changes the contents of the dynamically allocated heap memory It uses the library function strncpy() to make sure the memory is not corrupted, even if the client code

erroneously supplies a string that is longer than the size of the dynamic memory allocated for the object In case of overflow, strncpy() does not terminate the string with the null terminator This

is why I do that at the end of the function This seems superfluous in the case when the new string

is shorter than the available memory Keep in mind that in this case, strncpy() fills the rest of the string with zeros anyway and doing it once more will not slow down the program

Function modify() cannot expand the string over the initial length Most String designs do not allow the programmer to change the contents of the String object In this case, create and use another object for the different contents you need I am implementing the compromise Full-fledged modification facilities would require much more code and would require the discussion of many extraneous issues This little function modify() is sufficient for the purposes of this discussion

Function show() returns the pointer to the dynamically allocated memory Listing 11.2

demonstrates two uses of this function by the client code in main(). The first use is to print the contents of the String object that is the target of the show() message The second use is to modify the object contents by using the return value of the show() function as the output parameter in a call to strcpy() in the client code The first use is legitimate; the second use is arrogant and

written to intimidate the maintainer rather than to help the maintainer understand the intent of the code developer

One of the first high-level computer languages, APL (A Programming Language) was very

complex It is still used, mostly for financial applications The character set of this language is so large that it needs a special keyboard Among other things, it includes powerful operations for array and matrix processing APL programmers love this language It is considered good taste to write a few lines of APL code, show it to a friend, and ask, "Guess what it means?"

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 17

I am far from suggesting that programmers with such a mindset be fired from their jobs But they should not participate in group projects where other people have to maintain their code Today, there is nothing to boast about if a programmer writes code that needs extra effort to understand.

strcpy(v.show(),"Hi there"); // bad practice

Notice that my indignation is directed mostly at the fact that the maintainer has to spend extra effort

to understand the code That this code does not evaluate the size of the heap memory available within the object and hence can corrupt memory is important, but it only adds insult to injury It can

be corrected by using a different division of responsibilities between the client code and the server

String.

int length = strlen(v.show()); // get available space

strncpy(v.show(),"Hi there",length); // pushes responsibility up

For String objects created with the second conversion constructor, the value of length is the total available space For objects created with the first conversion constructor, the value of length gets the length of the last string stored, which could be less than the total available space Most

important, this method violates the principle of pushing responsibility from clients to servers and hiding details of data manipulation from the client code

Here, it is the client code that does low-level data manipulation, even if the names of String data members are not used in the code If you want to protect heap data from corruption, it is the server code that should include statements that evaluate the available size of dynamic storage A good solution should use the name of the server function rather than manipulating server data directly and should push the responsibility for protecting heap memory to the server Is it clear what I

mean? Here is a solution that does the job well, is safe, and needs no explanation You already saw this solution

Trang 18

Figure 11-6 Output for program in Listing 11.2

The use of the pointer returned by the function show() is not protected Here is an example of memory corruption that function String::show() makes possible

char *ptr = v.show(); // reckless practice

ptr[200] = 'A'; // memory corruption

This is not a good practice

Protecting Object Heap Data from Client Code

C++ provides you with a way to protect the internals of the object from the client code that uses the pointer returned by a member function Defining the pointer as a pointer to a constant prevents this abuse For example, define the returned value of function show() as a pointer to a constant

character rather than as a pointer to a nonconstant character, as I did in Listing 11.2

Trang 19

With this design of the server class String, the client code is forced to use modify() to change the state of the object As a result, the client code is expressed in terms of the server function call,

pushes protection operations down to the server class, and does not force the client to deal with the details of server design (limited heap space)

Overloaded Concatenation Operator

My next step is to design the overloaded operator function that concatenates two String objects: appending the contents of the second object to the contents of the first object This means that client code can use this overloaded operator function in the following way

String u("This is a test "); // left operand

String v("Nothing can go wrong."); // right operand

u += v; // expression: operand, operator, operand

void operator += (const String s); // concatenate parameter to target object

I know that it is not nice to pass objects by value, but I assume that there is no performance

problem here After all, the object of type String has only two small data members, a character

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 20

pointer and an integer Copying these data members should not take too long.

The algorithm for String concatenation should include the following steps

1. Add the length of both character arrays to define the total number of characters

2. Allocate heap memory to accommodate the characters and the terminating zero

3. Test for success of memory allocation; give up if the system is out of memory

4. Copy characters from the target object into the newly allocated heap space

5. Concatenate characters from the parameter object into the newly allocated space

6. Set the str pointer of the target object to point to the newly allocated space

Figure 11-7 shows these steps (with the exception of giving up if the system is out of heap

memory) and the C++ statements that implement them I use somewhat shorter strings in the client code to make the tracing of events simpler

Figure 11-7 The memory diagram for the String concatenation operator function.

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 21

The top part of the figure shows two String objects, u (with contents "Hi") and v (with contents

"there!") Part A shows both objects after the field len of the first object was modified, heap

memory was allocated, and the existing contents of object u was copied into this heap memory (steps 1¡V4 of the algorithm) Part B shows the state of the heap memory after step 5 Part C shows the state of the objects after the pointer str of the target object u was set to point to the newly allocated heap memory (step 6)

Putting it all together, you get the following server code

void String::operator += (const String s) // object parameter

{ char* p; // local pointer

len = strlen(str) + strlen(s.str); // total length

p = new char[len + 1]; // allocate heap memory

if (p==NULL) exit(1); // test for success

strcpy(p,str); // copy the first part of result strcat(p,s.str); // concatenate the second part str = p; } // set str to point to new

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 22

It might look like overkill to spell out the steps of this simple algorithm in such minute detail and to draw a separate picture for each small step of memory manipulation If you feel this way, great But you belong to a lucky minority For most people, pointer operations are obscure and

The drawings are, of course, only tools It is you who have to use the tools to make sure that you understand each statement

Preventing Memory Leaks

As I mentioned, Figure 11-7 shows that the heap character array pointed to by the target pointer

str at the beginning of the function call is not returned properly It becomes inaccessible when pointer str is turned to point to the newly allocated segment of memory (where the local pointer p

is pointing) This is memory leak¡Xa common error in pointer manipulation and memory

management To prevent memory leak, this character array has to be returned to the heap before the pointer str is turned to point to the newly allocated array

void String::operator += (const String s) // object parameter

{ char* p; // local pointer

len = strlen(str) + strlen(s.str); // total length

p = new char[len + 1]; // allocate enough heap memory

if (p==NULL) exit(1); // test for success

strcpy(p,str); // copy the first part of result strcat(p,s.str); // concatenate the second part delete str; // return existing dynamic memory str = p; } // set str to point to new memory

Trang 23

turned to point to the new heap array.

Figure 11-8 The memory diagram for the corrected String concatenation operator

function.

With memory leak taken care of, let me admit that in the discussion of this overloaded operator function I told you the truth and only the truth; but I did not tell you the whole truth The reason is that I wanted to make sure that I took care of smaller and less difficult issues before we faced more complex and more dangerous problems I would like to have your undivided attention

This discussion should show you the pattern of dangerous features you have to recognize when you

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 24

write your own C++ programs The core of the problem is my favorite enemy: passing objects as value parameters.

Protecting Program Integrity

When the actual argument, object or no object, is passed by value, its value is copied into a local automatic variable on the stack This copying is done memberwise

This presents no problem for arguments of built-in types but does present a minor performance nuisance for a simple class like Rational or Complex. It presents a real performance problem for larger classes whose objects require larger amounts of memory

Most important, this presents a huge integrity problem if the class has data members that are

pointers pointing to dynamically allocated heap memory Let us look at the execution of the

function with the value parameter at the crucial moments of function execution¡Xat the beginning

of the function call and at the function termination I like to attach these moments to the opening and closing braces of the function body

When a copy of the actual argument object is created during the pass by value, the system-supplied copy constructor is called This constructor copies the data members of the actual argument into the corresponding data members of its local copy, the formal parameter object When the pointer data member str is copied, the pointer in the formal object receives the value stored in the pointer in the actual argument object, that is, the address of heap memory allocated for the actual argument

object

As a result, pointers in both the actual argument and in its local copy point to the same section of the heap memory, and each object thinks that it has the exclusive use of this memory

I tried to represent this situation in Figure 11-9 Actually, what I told you does not change the

workings of the overloaded operator function (so far) This is why I noticed that all that I told you earlier was the truth and only the truth

Figure 11-9 The memory diagram for passing the String object by value.

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 25

Figure 11-9, which shows the whole truth, includes a local object s whose data members are

initialized to the values of the actual argument v. Figure 11-9(a) shows that this local object v and the actual argument u share the same section of the heap memory Figure 11-9(b) shows that after the new heap memory has been allocated and initialized and replaced the existing heap memory in the target object, the local object s and the argument u continue to share the same section of the heap memory

The whole truth should also include the termination of the function When the function execution reaches the closing brace of its scope and the function terminates, the local copy object (String s)

is destroyed From the point of view of conventional programming intuition, this means that the object memory (the pointer and the integer in this case) disappear However, there is no such thing

in C++ as a destruction of the object Each object destruction is preceded by a function call: a

destructor call

When the destructor is called, it does what the destructor code says it should do: It returns the

segment of memory pointed to by the object pointer

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 26

overloaded operator function terminates, the target object is in exactly the same state as during the previous discussion reflected in Figure 11-8 This client code produces the correct results.

But the memory returned by the destructor when the formal parameter s is destroyed did not belong

to that object It belonged (and still should belong) to the actual argument, that is, to object v

defined in the client space After the function call, the client object that is used as the actual

argument for the pass by value is robbed of its dynamically allocated memory It is an error for the client code to use it after the call

String u("Hi "); String v("there!");

cout << " u = " << u.show() << endl; // it displays "Hi "

cout << " v = " << v.show() << endl; // it displays "there!"

u += v;

cout << " u = " << u.show() << endl; // it displays "Hi there!"

cout << " v = " << v.show() << endl; // displays what it wants

It does not look particularly smart to recheck the value of the object v that was just printed and used

as an rvalue in the function call to operator+=(). I am doing this only because I know there is a problem with this implementation Clearly, the object has to have the same value that it had when it participated as an operand in the expression u+=v. This is the conventional programming intuition, and it works in C++ most of the time¡Xbut not all the time, and you should develop an alternative intuition as soon as possible I am telling you all that because in this innocent-looking client code, the value of the text for the object v can be anything, and any use of this object that assumes it is in

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 27

the same state as before is reckless.

How do you like this? Sure, C++ programming is not boring But a C++ programmer must

understand what is going on under the hood of a simple program like the snippet code in the last example

This is not the end of the story: that takes place at yet another closing scope brace Always pay attention to scope braces¡Xthey do a lot of work When the client code reaches the closing brace of its scope and terminates, the class destructors are called for all local objects, including that hapless object v, which was used as the actual argument in the function call and robbed of its dynamic memory when the call terminated The destructor tries to deallocate the area pointed to by the

object data member str. This memory, however, has already returned to the system If you were designing the language, you would have made it a "no op." No such luck In C++, repeated use of the delete operator on the same pointer is prohibited It is an error

Unfortunately, "an error" does not mean that the compiler produces a syntax error for you to

correct The compiler writer is not responsible for tracing the flow of execution and telling you that you made an error: The code is syntactically correct It also does not mean that the program

compiles, runs, and produces repeatable incorrect results It simply means that the results of such an attempt are "not defined." Actually, they are platform dependent How the application acts depends

on the operating system The system might crash, the program might run incorrectly (quietly), or it might run correctly until some time in the future

Listing 11.3 shows the complete program that implements this bad design The output of the

program as it appeared on my machine is shown in Figure 11-10

Figure 11-10 Output of the program in Listing 11.3

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 28

Example 11.3 Overloaded concatenation function with a value parameter.

~String (); // deallocate dynamic memory

void operator += (const String); // concatenate another object

void modify(const char*); // change the array contents

const char* show() const; // return a pointer to array

Trang 29

if (str==NULL) exit(1); // test for success

strcpy(str,s); } // copy incoming text into heap memory

String::~String()

{ delete str; } // return heap memory (not the pointer!)

void String::operator += (const String s) // pass by value

{ len = strlen(str) + strlen(s.str); // total length

char *p = new char[len + 1]; // allocate enough heap memory

if (p==NULL) exit(1); // test for success

strcpy(p,str); // copy the first part of result strcat(p,s.str); // add the second part of result delete str; // important step

str = p; } // now p can disappear

const char* String::show() const // protect data from changes

{ return str; }

void String::modify(const char a[]) // no memory management here

{ strncpy(str,a,len-1); // protect from overflow

str[len-1] = 0; } // terminate String properly

int main()

{ String u("This is a test ");

String v("Nothing can go wrong.");

cout << " u = " << u.show() << endl; // result is ok

cout << " v = " << v.show() << endl; // result is ok

u += v; // u.operator+=(v);

cout << " u = " << u.show() << endl; // result is ok

cout << " v = " << v.show() << endl; // result is not ok

v.modify("Let us hope for the best."); // memory corruption

cout << " v = " << v.show() << endl; // ????

return 0;

}

Notice that all these bad things happen at the function termination The first bad thing happened when the server-overloaded function operator+=() was terminating, and the destructor for the formal parameter was called¡Xthe actual argument v was robbed of its heap memory The second bad thing happened when the client function main() was terminating, and the object v was going out of scope¡Xits heap memory was repeatedly deleted

Actually, in C++, it is the repeated deleting of heap memory that is "an error." Deleting a NULL pointer is not an error This is "no operation." Some programmers tried to fix this problem by setting the pointer to heap memory in the destructor to NULL

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 30

String::~String()

{ delete str; // return heap memory

str = 0; } // set to null to avoid double deletion

This is a nice idea, but it does not work as intended The pointer that is set to zero belongs to the object that will be destroyed in several microseconds It is the second pointer that is pointing to the same memory that could be set to zero, but it is not available from the destructor executing on

another object Even if it worked, it would only prevent "an error." It would not restore the memory that was incorrectly deleted

How to Get There from Here

Did I scare you? If I did, this was my intent If I did not, regardless, make sure that you always worry about dynamic memory management in your programs Even if the programs run on your machine correctly, this is not evidence that the program is correct (add this to the list of your testing principles)

The program might run without a hitch for months and years, and then, after you install some other unrelated application or upgrade to the next version of Windows™ the use of memory changes and your program crashes Or it produces incorrect results that may not be noticed because it has run correctly for months and even years What do you do? Curse Microsoft because you just upgraded your operating system? But it is not Microsoft's fault! It is a fault of a C++ programmer who

neglected to put one symbol, an ampersand, in the interface of the overloaded operator function

operator+=().

This is how this function should look It does not pass its object parameter by value; it passes it by reference

void String::operator += (const String &s) // reference parameter

{ len = strlen(str) + strlen(s.str); // total length

char *p = new char[len + 1]; // allocate enough heap memory

if (p==NULL) exit(1); // test for success

strcpy(p,str); // copy the first part of result strcat(p,s.str); // add the second part of result delete str; // important step

str = p; } // now p can disappear

Figure 11-11 shows the output of the program in Listing 11.3 with the concatenation function that

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 31

passes its parameter by reference.

Figure 11-11 Output of the program in Listing 11.3 with the concatenation operator that

passes its parameter by reference.

Make sure that you run this program, experiment with it, and understand the issues that may cause problems Resist the urge to pass objects by value, unless, of course, you absolutely have to

It is quite disheartening that adding or removing just one single character in the source code (the ampersand) can change the behavior of the program so dramatically Notice that both versions of the code are syntactically correct¡Xthe compiler does not tell you there is any problem to worry about

Passing object parameters by value is like driving a tank You will get where you want to go, but you will cause a lot of indirect damage As I said earlier, resist passing objects by value; unless, of

course, you absolutely have to

ALERT

Do not pass objects to functions by value If the objects have internal pointers and handle heap memory dynamically, do not even think about passing objects to functions by value Pass them by reference And do not forget to use the const modifier if the function does not change the state of the parameter object and the state of the target object

More on the Copy Constructor

Let us look back at the situation The core of the problems discussed in the previous section is

copying an object whose data members are pointers to heap memory

Each object instance is supposed to point to the area of memory that is allocated specifically for it For example, class String has a pointer that points to the area of heap memory that contains

characters associated with the individual String object

When data members of one object are copied into data members of another object, the

corresponding pointers in both objects will have the same contents Hence, they will point to the

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 32

same area of heap memory These objects can die at different moments in time; for example, the formal value parameter of a function in Listing 11.3 disappears when the function terminates, and the actual argument continues to exist in the client space, function main(). When one object dies, its destructor deletes the memory pointed to by the object pointer(s), and the second object, still alive, silently loses its heap data Any use of this object that relies on heap data is incorrect It is "an error."

If this memory returned to the heap is not immediately reused for other purposes, this "phantom" object might behave as if the deleted memory still exists Your testing might persuade you that the program is correct

When the second object dies, its destructor is called Notice that I am not saying that the destructor

"is called again." The destructor was called earlier for a different object (the formal parameter), the one that was already destroyed Now the destructor is called for the second object (the actual

argument), and it tries to delete the same segment of heap memory In C++, this results in an error The program's behavior is undefined This is a polite way of saying that the program can do

whatever it wants

Remedies for the Integrity Problem

There are a number of remedies one can use to avoid trouble when objects with dynamically

allocated memory are passed as value parameters

One remedy is to eliminate the destructor that returns the heap memory to the system This is

neither a good solution nor is it a good permanent solution You might want to use it as a temporary solution when your program crashes and you need it to run so that you can debug it Turning off the destructor will let your program run to completion

Another remedy is to use fixed-size arrays inside objects rather than dynamically allocated

memory This is not an elegant solution, but it might do if the size of the array is allocated

generously This is especially true for programs that handle a relatively small number of objects, and the occasional truncation of data that do not fit into a fixed size is acceptable from the point of view of the integrity of the application

For parameter passing, the best remedy is passing object parameters by reference rather than by value It eliminates the problems created by copying objects It also speeds up program execution

by eliminating the need to create and destroy temporary objects and to call constructors and

Trang 33

cases where one class object is initialized by another object of the same class Consider the

following segment of code, which passes the parameter to operator+=() by reference

String u("This is a test "), v("Nothing can go wrong.");

cout << " u = " << u.show() << endl; // result is ok

cout << " v = " << v.show() << endl; // result is ok

u += v; // u.operator+=(v); by reference cout << " u = " << u.show() << endl; // result is ok

cout << " v = " << v.show() << endl; // ok: pass by reference

v.modify("Let us hope for the best."); // no memory corruption

String t = v; // object initialization

cout << " t = " << t.show() << endl; // ok: correct result

t.modify("Nothing can go wrong."); // change both t and v

cout << " t = " << t.show() << endl; // ok: correct result

cout << " v = " << v.show() << endl; // v also changed

Figure 11-12 Expected (not real) output of the snippet of client code above.

Things in real life, however, are not always as we expect them to be Listing 11.4 shows the code for class String (with the parameter to the overloaded operator function operator+=() passed by reference) and the client code that implements the previous snippet of code I modified the snippet

so that object t is created in a nested scope When this nested code terminates and object t

disappears, I can check the state of object v and verify its integrity Figure 11-13 shows the real results of the execution of the program in Listing 11.4

Figure 11-13 Output of the program in Listing 11.4

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 34

Example 11.4 Initializing one object with data from another object.

String (int length=0); // conversion/default constructor

String(const char*); // conversion constructor

~String (); // deallocate dynamic memory

void operator += (const String&); // concatenate another object

void modify(const char*); // change the array contents

const char* show() const; // return a pointer to the array

Trang 35

{ len = strlen(s); // measure the length of incoming text

str = new char[len+1]; // allocate enough heap space

if (str==NULL) exit(1); // test for success

strcpy(str,s); } // copy incoming text into heap memory

String::~String()

{ delete str; } // return heap memory (not the

pointer!)

void String::operator += (const String& s) // reference parameter

{ len = strlen(str) + strlen(s.str); // total length

char* p = new char[len + 1]; // allocate enough heap memory

if (p==NULL) exit(1); // test for success

strcpy(p,str); // copy the first part of result strcat(p,s.str); // add the second part of result delete str; // important step

str = p; } // now temp can disappear

const char* String::show() const // protect data from changes

{ return str; }

void String::modify(const char a[]) // no memory management here

{ strncpy(str,a,len-1); // protect from overflow

str[len-1] = 0; } // terminate String properly

int main()

{ cout << endl << endl;

String u("This is a test ");

String v("Nothing can go wrong.");

cout << " u = " << u.show() << endl; // result is ok

cout << " v = " << v.show() << endl; // result is ok

u += v; // u.operator+=(s);

cout << " u = " << u.show() << endl; // result is ok

cout << " v = " << v.show() << endl; // ok: pass by reference

v.modify("Let us hope for the best."); // no memory corruption

{ String t = v; // initialization

cout << " t = " << t.show() << endl; // ok: correct result

t.modify("Nothing can go wrong."); // change both t and v

cout << " t = " << t.show() << endl; // ok: correct result

cout << " v = " << v.show() << endl; } // v also changed

cout << " v = " << v.show() << endl; // t died, v is robbed

Trang 36

constructor is called You see the assignment sign in the client code, but this is not an

assignment¡Xit is an initialization As I said earlier, it is not a question whether a constructor is called after the object is created It is a question of which constructor is called The answer is that it depends on the data that the client code supplies when the object is created In Listing 11.4, the client main() supplies one actual argument, existing object v. Hence, it is a constructor with one parameter that is an object of the same type to which the constructor belongs, in this case, class

String.

What is the name of the constructor with one parameter of the class type? As you might recall from

Chapter 9, "C++ Class as a Unit of Modularization," it's a copy constructor because it copies data from one object into another object However, class String does not have a copy constructor Does that mean that an attempt to call this missing copy constructor generates a syntax error? No, the compiler generates a call to the system-provided copy constructor The compiler provides the

constructor, and the compiler generates the call This constructor copies the fields of the argument object into the fields of the object being created For class String, this system-provided copy constructor looks this way:

String::String(const String& s) // system-provided constructor

{ len = s.len; // copy the length of the object text

str = s.str; } // copy the pointer to the object text

Figure 11-14 shows how this constructor works When the String object t is created, its field len

is set to 9, and its field str is set to point to the same area of heap memory that the pointer str of object v is pointing to

Figure 11-14 The memory diagram for initializing one String object with data from

another object

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 37

Similar to the earlier story about parameter passing, the two objects, t and v, have only one

segment of heap memory between them, not two This segment was allocated earlier for object v,

but now it is shared by object t. And each object thinks that this heap area belongs to it alone This situation is even worse than pass by value In pass by value, the actual argument exists in the client scope, and the formal parameter exists in the server scope At each moment of execution, only one object is available Here, both objects exist in the same client scope, and they both can be modified and accessed in the same scope

Since these two objects share the same area of heap memory, they are synonyms from the point of view of client code This is why when object t is modified by client code, object v is modified, too

Is this clear from Figure 11-14? Do you see this on the program output in Figure 11-13? From the point of view of common programming intuition, there is no reason why object v should change in the client code But it does change

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 38

Come to think of it, this is strange only from the point of view of common programming intuition

In introductory programming classes, I often meet students who have trouble with simple code dealing with integers

In a sense, they have a point If the variables are synonyms, changing to one is visible through another As you may recall, this is quite common if one variable is a regular variable, and another variable is a reference

int v = 10; int& t = v; t = 20; // what is v now?

In this example, common programming intuition does not work It is the novice's logic that applies

We make a commitment for these two variables, v and t, to be the same Small wonder that v

changes after a change in t. Now v is 20 This is the logic that all C++ programmers, novices and experts alike, should become comfortable with

Copy Semantics and Value Semantics

Actually, there are two kinds of common programming intuition that correspond to two different

computer science concepts, value semantics and reference semantics (Semantics here means the

meaning of copying data.)

The more common programming intuition uses value semantics Each computational object (e.g., a variable of a built-in type or an object of programmer-defined type) has its own separate area in memory Equating two computational objects means repeating the bit pattern in another object's memory In C++ (as in most other programming languages), value semantics is used for both built-

in variables and objects of programmer-defined classes

Trang 39

This is why this intuition is more common: From its point of view, when two objects have the same value, they have two separate bit patterns, and changing the value of one object should not affect the bit pattern already existing in the other object.

Another, less common programming intuition, uses reference semantics When a computational object is assigned a value, it receives a reference (or a pointer) to that value Equating

computational objects means setting their references (or pointers) to point to the same location in memory When the character array pointed to in one object changes, the second object sees the change because both pointers point to the same location In C++, the reference semantics is used for pointers and references, in passing parameters by reference or by pointer, for arrays, and for linked data structures with pointers

invitation, as in this example, and you should be ready to recognize it and to deal with it

appropriately A C++ programmer should always think about the difference between value and reference semantics

This is not the end of trouble with the program in Listing 11.4 When the execution reaches the closing brace of the nested scope (my favorite topic of discussion during analysis of code

behavior), the object t is going to disappear because it is defined in this nested scope The object v

is defined in the enclosing scope of the function main(), and it should be available for further use

In Listing 11.4, I am trying to print the value of v at the end of main(). Notice that this statement

is separated from the preceding printing statement only by the closing brace of the nested scope On the surface, no event is taking place between these two statements in the client code, and hence these two printing statements should produce the same output¡Xbut they do not Again, traditional programming intuition is not sufficient for understanding a C++ program, and you have to develop your own intuition to help you read code segments like this

As you see in Figure 11-14, the first statement produces a legible output It is not what you would normally expect, but at least it is there The second statement produces garbage What happened between these two statements? When the closing brace of the nested scope was reached, the String

destructor was called for the local object t in the nested scope As you see from Listing 11.4 and from Figure 11-14, this destructor deleted the heap memory pointed to by the pointer str of object

t. This dynamic memory actually belongs to object v, but the system does not remember that It only remembers that the memory pointed to by pointer str should be deleted in accordance with

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 40

the code of the String destructor The object v is robbed of its dynamic memory, but nobody knows about it The object is formally in scope and appears to be in good health But it is only appearance: It cannot be utilized for anything useful by the client code.

This is similar to parameter passing by value, and like passing objects by value, this is not the end

of the story When the execution reaches the closing brace, the object v should disappear according

to the scope rules Before that, the destructor is called and it tries to delete the heap memory that was already deleted The program is incorrect It does what it wants (the program crashes)

Programmer-Defined Copy Constructor

Short of giving up dynamic memory management, this problem has only one solution¡Xa

programmer-defined copy constructor The constructor should allocate heap space for the target object similar to the concatenation operator that was discussed in the previous section Here is its algorithm:

1. Copy the length of the parameter's character arrays into the target's len.

2. Allocate heap memory; set target's pointer str to point to it

3. Test for success of memory allocation; give up if the system is out of memory

4. Copy characters from the target object into the newly allocated space

Here is a programmer-defined copy constructor that is a solution to the problem

String::String(const String& s) // programmer-defined copy constructor { len = s.len; // length of the source text

str = new char[len+1]; // request separate heap memory

if (str == NULL) exit(1); // test for success

strcpy(str,s.str); } // copy the source text

Notice that the parameter s is passed by reference It is a reference to the actual argument object

No copy of argument data members is made in parameter passing Instead, dynamic memory is allocated for the target object inside the constructor Dynamic memory of the actual argument object is copied into the target's heap memory

This is less efficient than memberwise copying in Listing 11.4 Value semantics is slower than reference semantics because it operates on values, not on references or pointers However, value semantics is safe Recall the client code that caused all the problems

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Ngày đăng: 12/08/2014, 11:20

TỪ KHÓA LIÊN QUAN