Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ For Dummies (2004) [eng].pdf
Скачиваний:
84
Добавлен:
16.08.2013
Размер:
8.09 Mб
Скачать

242 Part III: Introduction to Classes

the In function... message. The copied Student object copy is destruc­ ted at the return from fn(). The original object, randy, is destructed at the end of main().

The copy constructor here is flagged with comments to allow you to see the process. This copy constructor first copies the string Copy of into its name field. It then copies the name string from the source object s into the current object. The constructor outputs the resulting name field before returning.

The first line of output shows the chester object being created. The third line demonstrates the copy Student being generated from the copy con­ structor. Once within the function, it does nothing more than output a mes­ sage. The copy is destructed as part of the return, which generates the destructing... message.

The Automatic Copy Constructor

Like the default constructor, the copy constructor is important, important enough that C++ thinks no class should be without one. If you don’t provide your own copy constructor, C++ generates one for you. (This differs from the default constructor that C++ provides unless your class has constructors defined for it.)

The copy constructor provided by C++ performs a member-by-member copy of each data member. The copy constructor that early versions of C++ pro­ vided performed a bit-wise copy. The difference is that a member-by-member copy invokes all copy constructors that might exist for the members of the class, whereas a bit-wise copy does not. You can see the effects of this differ­ ence in the following DefaultCopyConstructor sample program. (I left out the definition of the Student class to save space — it’s identical to that shown in the CopyConstructor program. The entire DefaultCopyConstructor program is included on this book’s CD-ROM.)

//

//DefaultCopyConstructor - demonstrate that the default

//

copy constructor invokes the

//

copy constructor for any member

//

 

#include <cstdio> #include <cstdlib> #include <iostream> using namespace std;

class Tutor

{

public:

Tutor(Student& s) : student(s)

//

invoke

copy

{

// constructor

on

member

student

Chapter 18: Copying the Copy Copy Copy Constructor 243

cout << “constructing Tutor object” << endl; id = 0;

}

protected: Student student; int id;

};

void fn(Tutor tutor)

{

cout << “In function fn()” << endl;

}

int main(int argcs, char* pArgs[])

{

Student chester(“Chester”); Tutor tutor(chester);

cout << “Calling fn()” << endl; fn(tutor);

cout << “Returned from fn()” << endl;

//wait until user is ready before terminating program

//to allow the user to see the program results system(“PAUSE”);

return 0;

}

Executing this program generates the following output:

constructed Chester constructed Copy of Chester constructing Tutor object Calling fn()

constructed Copy of Copy of Chester In function fn()

destructing Copy of Copy of Chester Returned from fn()

Press any key to continue . . .

Constructing the chester object generates the first output message from the “plain Jane” constructor. The constructor for the tutor object invokes the Student copy constructor in order to generate its own student data member. This accounts for the next two lines of output.

The program then passes a copy of the Tutor object to the function fn() (pronounced “fun,” by the way). Because the Tutor class does not define a copy constructor, the program invokes the default copy constructor to make a copy to pass to fn().

The default Tutor copy constructor invokes the copy constructor for each data member. The copy constructor for the int “class” does nothing more than copy the value. However, you’ve already seen how the Student copy

244 Part III: Introduction to Classes

constructor works. This is what generates the constructed Copy of Copy of Chester message. The destructor for the copy is invoked as part of the return from function fn().

Creating Shallow Copies versus Deep Copies

Performing a member-by-member copy seems the obvious thing to do in a copy constructor. Other than adding the capability to tack on silly things such as “Copy of “ to the front of students’ names, when would you ever want to do anything but a member-by-member copy?

Consider what happens if the constructor allocates an asset, such as memory off the heap. If the copy constructor simply makes a copy of that asset with­ out allocating its own, you end up with a troublesome situation: two objects thinking they have exclusive access to the same asset. This becomes nastier when the destructor is invoked for both objects and they both try to put the same asset back. To make this more concrete, consider the following example class:

//

//ShallowCopy - performing a byte-by-byte (shallow) copy

//

is not correct when the class holds assets

//

 

#include <cstdio> #include <cstdlib> #include <iostream> #include <strings.h> using namespace std;

class Person

{

public: Person(char *pN)

{

cout << “constructing “ << pN << endl; pName = new char[strlen(pN) + 1];

if (pName != 0)

{

strcpy(pName, pN);

}

}

~Person()

{

cout << “destructing “ << pName << endl; strcpy(pName, “already destructed memory”); // delete pName;

}

Chapter 18: Copying the Copy Copy Copy Constructor 245

protected: char *pName;

};

void fn()

{

// create a new object

Person p1(“This_is_a_very_long_name”);

// copy the contents of p1 into p2 Person p2(p1);

}

int main(int argcs, char* pArgs[])

{

cout << “Calling fn()” << endl; fn();

cout << “Returned from fn()” << endl;

//wait until user is ready before terminating program

//to allow the user to see the program results system(“PAUSE”);

return 0;

}

This program generates the following output:

Calling fn()

constructing This_is_a_very_long_name destructing This_is_a_very_long_name

destructing already destructed memory Returned from fn()

Press any key to continue . . .

The constructor for Person allocates memory off the heap to store the person’s name, rather than put up with some arbitrary limit imposed by a fixed-length array. However, the destructor copies a message into this memory buffer rather than put it back on the heap. The main program calls the function fn(), which creates one person, p1, and then makes a copy of that person, p2. Both objects are destructed automatically when the program returns from the function.

Only one constructor output message appears when this program is executed. That’s not too surprising because the C++ provided copy constructor used to build p2 performs no output. As p1 and p2 go out of scope, you don’t receive the two output messages that you might have expected. The first destructor outputs the expected This_is_a_very_long_name. However, the second destructor indicates that the memory has already been deleted.

If you really were to delete the name, the program would become unstable after the second delete and might not even complete properly without crashing.

246 Part III: Introduction to Classes

The constructor is called once and allocates a block of memory off the heap to hold the person’s name. The copy constructor provided by C++ copies that address into the new object without allocating a new block.

The problem is shown in Figure 18-1. The object p1 is copied into the new object p2, but the assets are not. Thus, p1 and p2 end up pointing to the same assets (in this case, heap memory). This is known as a shallow copy because it just “skims the surface,” copying the members themselves.

 

p1

p1

 

pName

pName

 

heap

heap

 

memory

memory

Figure 18-1:

 

p2

 

pName

Shallow

 

 

 

copy of

Before copy

After copy

p1 to p2.

 

 

The solution to this problem is demonstrated visually in Figure 18-2. This figure represents a copy constructor that allocates its own assets to the new object. The following shows an appropriate copy constructor for class Person, the type you’ve seen up until now. (This class is embodied in the program DeepCopy, which is on this book’s CD-ROM.)

class Person

{

public: Person(char *pN)

{

cout << “constructing “ << pN << endl; pName = new char[strlen(pN) + 1];

if (pName != 0)

{

strcpy(pName, pN);

}

}

// copy constructor allocates a new heap block // from the heap

Person(Person& p)

{

cout << “copying “ << p.pName

<< “ into its own block” << endl; pName = new char[strlen(p.pName) + 1];

Chapter 18: Copying the Copy Copy Copy Constructor 247

if (pName != 0)

{

strcpy(pName, p.pName);

}

}

~Person()

{

cout << “destructing “ << pName << endl; strcpy(pName, “already destructed memory”); // delete pName;

}

protected: char *pName;

};

Here you see that the copy constructor allocates its own memory block for the name and then copies the contents of the source object name into this new name block. This is a situation similar to that shown in Figure 18-2. Deep copy is so named because it reaches down and copies all the assets. (Okay, the analogy is pretty strained, but that’s what they call it.)

The output from this program is as follows:

Calling fn()

constructing This_is_a_very_long_name

copying This_is_a_very_long_name into its own block destructing This_is_a_very_long_name

destructing This_is_a_very_long_name Returned from fn()

Press any key to continue . . .

 

p1

p1

 

pName

pName

 

heap

heap

 

memory

memory

 

 

p2

 

 

pName

 

 

heap

Figure 18-2:

 

memory

 

 

Deep copy

Before copy

After copy

of p1 to p2.

248 Part III: Introduction to Classes

The destructor for Person now indicates that the string pointers in p1 and p2 don’t point to common block of data. (Note, again, that the destructor out­ puts the most helpful “destructing...” message for debug purposes instead of actually doing anything.

It’s a Long Way to Temporaries

C++ generates a copy of an object to pass to a function by value. (This is described in the earlier sections of this chapter.) This is the most obvious but not the only example. C++ creates a copy of an object under other conditions as well.

Consider a function that returns an object by value. In this case, C++ must create a copy using the copy constructor. This situation is demonstrated in the following code snippet:

Student fn();

// returns object by value

int main(int argcs, char* pArgs[])

{

 

Student s;

 

s = fn();

// call to fn() creates temporary

// how long does the temporary returned by fn()last? return 0;

}

The function fn() returns an object by value. Eventually, the returned object is copied to s, but where does it reside until then?

C++ creates a temporary object into which it stuffs the returned object. “Okay,” you say. “C++ creates the temporary, but how does it know when to destruct it?” Good question. In this example, it doesn’t make much difference, because you’ll be through with the temporary when the copy constructor copies it into s. But what if s is defined as a reference:

int main(int argcs, char* pArgs[])

{

Student& refS = fn();

// ...now what?...

return 0;

}

It makes a big difference how long temporaries live because refS exists for the entire function. Temporaries created by the compiler are valid throughout the extended expression in which they were created and no further.

In the following function, I mark the point at which the temporary is no longer valid: