Ivor Horton’s BeginningVisual C++ 2008 phần 5 ppt

139 274 0
Ivor Horton’s BeginningVisual C++ 2008 phần 5 ppt

Đ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

How It Works In this example you calculate the volumes of the two CCandyBox objects by invoking the Volume() function that is a member of the derived class. This function accesses the inherited members m_Length, m_Width, and m_Height to produce the result. The members are declared as protected in the base class and remain protected in the derived class. The program produces the output shown as follows: CBox constructor called CCandyBox constructor1 called CBox constructor called CCandyBox constructor2 called myCandyBox volume is 1 myToffeeBox volume is 24 CCandyBox destructor called CBox destructor called CCandyBox destructor called CBox destructor called The output shows that the volume is being calculated properly for both CCandyBox objects. The first object has the default dimensions produced by calling the default CBox constructor, so the volume is 1, and the second object has the dimensions defined as initial values in its declaration. The output also shows the sequence of constructor and destructor calls, and you can see how each derived class object is destroyed in two steps. Destructors for a derived class object are called in the reverse order to the constructors for the object. This is a general rule that always applies. Constructors are invoked starting with the base class con- structor and then the derived class constructor, whereas the destructor for the derived class is called first when an object is destroyed, followed by the base class destructor. You can demonstrate that the protected members of the base class remain protected in the derived class by uncommenting the statement preceding the return statement in the function main(). If you do this, you get the following error message from the compiler, error C2248: ‘m_Length’: cannot access protected member declared in class ‘CBox’ which indicates quite clearly that the member m_Length is inaccessible. The Access Level of Inherited Class Members You know that if you have no access specifier for the base class in the definition of a derived class, the default specification is private. This has the effect of causing the inherited public and protected members of the base class to become private in the derived class. The private members of the base class remain private to the base and therefore inaccessible to member functions of the derived class. In fact, they remain private to the base class regardless of how the base class is specified in the derived class definition. You have also used public as the specifier for a base class. This leaves the members of the base class with the same access level in the derived class as they had in the base, so public members remain public and protected members remain protected. 522 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:54 AM Page 522 The last possibility is that you declare a base class as protected. This has the effect of making the inher- ited public members of the base protected in the derived class. The protected (and private) inherited members retain their original access level in the derived class. This is summarized in Figure 9-3. Figure 9-3 This may look a little complicated, but you can reduce it to the following three points about the inherited members of a derived class: ❑ Members of a base class that are declared as private are never accessible in a derived class. ❑ Defining a base class as public doesn’t change the access level of its members in the derived class. ❑ Defining a base class as protected changes its public members to protected in the derived class. Being able to change the access level of inherited members in a derived class gives you a degree of flexi- bility, but don’t forget that you cannot relax the level specified in the base class; you can only make the access level more stringent. This suggests that your base classes need to have public members if you want to be able to vary the access level in derived classes. This may seem to run contrary to the idea of encapsulating data in a class in order to protect it from unauthorized access, but, as you’ll see, it is often the case that you define base classes in such a manner that their only purpose is to act as a base for other classes, and they aren’t intended to be used for instantiating objects in their own right. inherited as inherited as public: public: protected: protected: protected: protected: private: private: private: inherited as No access - ever. class CBox class CCBox:private CBox class CBBox:protected CBox class CABox:public CBox inherited as inherited as inherited as ••• ••• ••• ••• ••• ••• ••• ••• ••• } { { } { } { } 523 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:54 AM Page 523 The Copy Constructor in a Derived Class Remember that the copy constructor is called automatically when you declare an object that is initialized with an object of the same class. Look at these statements: CBox myBox(2.0, 3.0, 4.0); // Calls constructor CBox copyBox(myBox); // Calls copy constructor The first statement calls the constructor that accepts three arguments of type double, and the second calls the copy constructor. If you don’t supply your own copy constructor, the compiler supplies one that copies the initializing object member by member to the corresponding members of the new object. So that you can see what is going on during execution, you can add your own version of a copy con- structor to the class CBox. You can then use this class as a base for defining the CCandyBox class. // Box.h in Ex9_05 #pragma once #include <iostream> using std::cout; using std::endl; class CBox // Base class definition { public: // Base class constructor CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0): m_Length(lv), m_Width(wv), m_Height(hv) { cout << endl << “CBox constructor called”; } // Copy constructor CBox(const CBox& initB) { cout << endl << “CBox copy constructor called”; m_Length = initB.m_Length; m_Width = initB.m_Width; m_Height = initB.m_Height; } // CBox destructor - just to track calls ~CBox() { cout << “CBox destructor called” << endl; } protected: double m_Length; double m_Width; double m_Height; }; Also recall that the copy constructor must have its parameter specified as a reference to avoid an infinite number of calls to itself, which would otherwise result from the need to copy an argument that is trans- ferred by value. When the copy constructor in our example is invoked, it outputs a message to the screen, so you’ll be able to see from the output when this is happening. 524 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:54 AM Page 524 We will use the version of CCandyBox class from Ex9_04.cpp, shown again here: // CandyBox.h in Ex9_05 #pragma once #include “Box.h” #include <iostream> using std::cout; using std::endl; class CCandyBox: public CBox { public: char* m_Contents; // Derived class function to calculate volume double Volume() const { return m_Length*m_Width*m_Height; } // Constructor to set dimensions and contents // with explicit call of CBox constructor CCandyBox(double lv, double wv, double hv, char* str = “Candy”) :CBox(lv, wv, hv) // Constructor { cout << endl <<”CCandyBox constructor2 called”; m_Contents = new char[ strlen(str) + 1 ]; strcpy_s(m_Contents, strlen(str) + 1, str); } // Constructor to set contents // calls default CBox constructor automatically CCandyBox(char* str = “Candy”) // Constructor { cout << endl << “CCandyBox constructor1 called”; m_Contents = new char[ strlen(str) + 1 ]; strcpy_s(m_Contents, strlen(str) + 1, str); } ~CCandyBox() // Destructor { cout << “CCandyBox destructor called” << endl; delete[] m_Contents; } }; This doesn’t have a copy constructor added yet, so you’ll be relying on the compiler-generated version. Try It Out The Copy Constructor in Derived Classes You can exercise the copy constructor that you have just defined with the following example: // Ex9_05.cpp // Using a derived class copy constructor #include <iostream> // For stream I/O #include <cstring> // For strlen() and strcpy() #include “CandyBox.h” // For CBox and CCandyBox 525 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:54 AM Page 525 using std::cout; using std::endl; int main() { CCandyBox chocBox(2.0, 3.0, 4.0, “Chockies”); // Declare and initialize CCandyBox chocolateBox(chocBox); // Use copy constructor cout << endl << “Volume of chocBox is “ << chocBox.Volume() << endl << “Volume of chocolateBox is “ << chocolateBox.Volume() << endl; return 0; } How It Works (or Why It Doesn’t) When you run the Debug version of this example, in addition to the expected output, you’ll see the dialog shown in Figure 9-4 displayed. Click Abort to clear the dialog box and you’ll see the output in the console window that you might expect. The output shows that the compiler-generated copy constructor for the derived class automatically called the copy constructor for the base class. However, as you’ve probably realized, all is not as it should be. In this particular case, the compiler- generated copy constructor causes problems because the memory pointed to by the m_Contents mem- ber of the derived class in the second object declared points to the same memory as the one in the first object. When one object is destroyed (when it goes out of scope at the end of main()), it releases the memory occupied by the text. When the second object is destroyed, the destructor attempts to release some memory that has already been freed by the destructor call for the previous object — and that’s the reason for the error message in the dialog box. The way to fix this is to supply a copy constructor for the derived class that allocates some additional memory for the new object. Figure 9-4 526 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 526 Try It Out Fixing the Copy Constructor Problem You can do this by adding the following code for the copy constructor to the public section of the derived CCandyBox class in Ex9_05: // Derived class copy constructor CCandyBox(const CCandyBox& initCB) { cout << endl << “CCandyBox copy constructor called”; // Get new memory m_Contents = new char[ strlen(initCB.m_Contents) + 1 ]; // Copy string strcpy_s(m_Contents, strlen(initCB.m_Contents) + 1, initCB.m_Contents); } You can now run this new version of the last example with the same function main() to see how the new copy constructor works. How It Works Now when you run the example, it behaves better and produces the following output: CBox constructor called CCandyBox constructor2 called CBox constructor called CCandyBox copy constructor called Volume of chocBox is 24 Volume of chocolateBox is 1 CCandyBox destructor called CBox destructor called CCandyBox destructor called CBox destructor called However, there is still something wrong. The third line of output shows that the default constructor for the CBox part of the object chocolateBox is called, rather than the copy constructor. As a consequence, the object has the default dimensions rather than the dimensions of the initializing object, so the volume is incorrect. The reason for this is that when you write a constructor for an object of a derived class, you are responsible for ensuring that the members of the derived class object are properly initialized. This includes the inherited members. The fix for this is to call the copy constructor for the base part of the class in the initialization list for the copy constructor for the CCandyBox class. The copy constructor then becomes: // Derived class copy constructor CCandyBox(const CCandyBox& initCB): CBox(initCB) { cout << endl << “CCandyBox copy constructor called”; // Get new memory 527 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 527 m_Contents = new char[ strlen(initCB.m_Contents) + 1 ]; // Copy string strcpy_s(m_Contents, strlen(initCB.m_Contents) + 1, initCB.m_Contents); } Now the CBox class copy constructor is called with the initCB object. Only the base part of the object is passed to it, so everything works out. If you modify the last example by adding the base copy constructor call, the output is as follows: CBox constructor called CCandyBox constructor2 called CBox copy constructor called CCandyBox copy constructor called Volume of chocBox is 24 Volume of chocolateBox is 24 CCandyBox destructor called CBox destructor called CCandyBox destructor called CBox destructor called The output shows that all the constructors and destructors are called in the correct sequence and the copy constructor for the CBox part of chocolateBox is called before the CCandyBox copy constructor. The volume of the object chocolateBox of the derived class is now the same as that of its initializing object, which is as it should be. You have, therefore, another golden rule to remember: If you write any kind of constructor for a derived class, you are responsible for the initialization of all members of the derived class object, including all its inherited members. Class Members as Friends You saw in Chapter 7 how a function can be declared as a friend of a class. This gives the friend func- tion the privilege of free access to any of the class members. Of course, there is no reason why a friend function cannot be a member of another class. Suppose you define a CBottle class to represent a bottle: class CBottle { public: CBottle(double height, double diameter) { m_Height = height; m_Diameter = diameter; } 528 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 528 private: double m_Height; // Bottle height double m_Diameter; // Bottle diameter }; You now need a class to represent the packaging for a dozen bottles that automatically has custom dimensions to accommodate a particular kind of bottle. You could define this as: class CCarton { public: CCarton(const CBottle& aBottle) { m_Height = aBottle.m_Height; // Bottle height m_Length = 4.0*aBottle.m_Diameter; // Four rows of m_Width = 3.0*aBottle.m_Diameter; // three bottles } private: double m_Length; // Carton length double m_Width; // Carton width double m_Height; // Carton height }; The constructor here sets the height to be the same as that of the bottle it is to accommodate, and the length and width are set based on the diameter of the bottle so that twelve fit in the box. As you know by now, this won’t work. The data members of the CBottle class are private, so the CCarton constructor cannot access them. As you also know, a friend declaration in the CBottle class fixes it: class CBottle { public: CBottle(double height, double diameter) { m_Height = height; m_Diameter = diameter; } private: double m_Height; // Bottle height double m_Diameter; // Bottle diameter // Let the carton constructor in friend CCarton::CCarton(const CBottle& aBottle); }; The only difference between the friend declaration here and what you saw in Chapter 7 is that you must put the class name and the scope resolution operator with the friend function name to identify it. For this to compile correctly, the compiler needs to have information about the CCarton class construc- tor, so you would need to put an #include statement for the header file containing the CCarton class definition before the definition of the CBottle class. 529 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 529 Friend Classes You can also allow all the function members of one class to have access to all the data members of another by declaring it as a friend class. You could define the CCarton class as a friend of the CBottle class by adding a friend declaration within the CBottle class definition: friend CCarton; With this declaration in the CBottle class, all function members of the CCarton class now have free access to all the data members of the CBottle class. Limitations on Class Friendship Class friendship is not reciprocated. Making the CCarton class a friend of the CBottle class does not mean that the CBottle class is a friend of the CCarton class. If you want this to be so, you must add a friend declaration for the CBottle class to the CCarton class. Class friendship is also not inherited. If you define another class with CBottle as a base, members of the CCarton class will not have access to its data members, not even those inherited from CBottle. Virtual Functions Look more closely at the behavior of inherited member functions and their relationship with derived class member functions. You could add a function to the CBox class to output the volume of a CBox object. The simplified class then becomes: // Box.h in Ex9_06 #pragma once #include <iostream> using std::cout; using std::endl; class CBox // Base class { public: // Function to show the volume of an object void ShowVolume() const { cout << endl << “CBox usable volume is “ << Volume(); } // Function to calculate the volume of a CBox object double Volume() const { return m_Length*m_Width*m_Height; } // Constructor CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0) :m_Length(lv), m_Width(wv), m_Height(hv) {} 530 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 530 protected: double m_Length; double m_Width; double m_Height; }; Now you can output the usable volume of a CBox object just by calling the ShowVolume() function for any object for which you require it. The constructor sets the data member values in the initialization list, so no statements are necessary in the body of the function. The data members are as before and are spec- ified as protected, so they are accessible to the member functions of any derived class. Suppose you want to derive a class for a different kind of box called CGlassBox, to hold glassware. The contents are fragile, and because packing material is added to protect them, the capacity of the box is less than the capacity of a basic CBox object. You therefore need a different Volume() function to account for this, so you add it to the derived class: // GlassBox.h in Ex9_06 #pragma once #include “Box.h” class CGlassBox: public CBox // Derived class { public: // Function to calculate volume of a CGlassBox // allowing 15% for packing double Volume() const { return 0.85*m_Length*m_Width*m_Height; } // Constructor CGlassBox(double lv, double wv, double hv): CBox(lv, wv, hv){} }; There could conceivably be other additional members of the derived class, but we’ll keep it simple and con- centrate on how the inherited functions work for the moment. The constructor for the derived class objects just calls the base class constructor in its initialization list to set the data member values. No statements are necessary in its body. You have included a new version of the Volume() function to replace the version from the base class, the idea being that you can get the inherited function ShowVolume() to call the derived class version of the member function Volume() when you call it for an object of the class CGlassBox. Try It Out Using an Inherited Function Now see how your derived class works in practice. You can try this out very simply by creating an object of the base class and an object of the derived class with the same dimensions and then verifying that the correct volumes are being calculated. The main() function to do this is as follows: // Ex9_06.cpp // Behavior of inherited functions in a derived class #include <iostream> #include “GlassBox.h” // For CBox and CGlassBox using std::cout; using std::endl; 531 Chapter 9: Class Inheritance and Virtual Functions 25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 531 [...]... the ‘abstract’ keyword 55 5 259 05c09.qxd:WroxPro 2/21/08 8 :55 AM Page 55 6 Chapter 9: Class Inheritance and Virtual Functions virtual double Volume() abstract; // Function to display a volume virtual void ShowVolume() { Console::WriteLine(L”Volume is {0}”, Volume()); } }; The first thing to note is the abstract keyword following the class name If a C++/ CLI class contains the native C++ equivalent of a pure... stores the value in n Note that without the safe_cast, this statement will not compile because there is no implicit conversion in this situation 55 4 259 05c09.qxd:WroxPro 2/21/08 8 :55 AM Page 55 5 Chapter 9: Class Inheritance and Virtual Functions Inheritance in C++/ CLI Classes Although value classes always have the System::Object class as a base, you cannot derive a value class from an existing class To... doing in previous chapters when you defined the ToString() function in a class 55 3 259 05c09.qxd:WroxPro 2/21/08 8 :55 AM Page 55 4 Chapter 9: Class Inheritance and Virtual Functions Because System::Object is a base class for all C++/ CLI classes, the handle type System::Object^ fulfils a similar role to the void* type in native C++ in that it can be used to reference any type of object Boxing and Unboxing... stream I/O using std::cout; using std::endl; const double PI = 3.14 159 2 65; // Global definition for PI int main() { // Pointer to abstract base class initialized with CBox object address 54 3 259 05c09.qxd:WroxPro 2/21/08 8 :55 AM Page 54 4 Chapter 9: Class Inheritance and Virtual Functions CContainer* pC1 = new CBox(2.0, 3.0, 4.0); CCan myCan(6 .5, 3.0); // Define CCan object CGlassBox myGlassBox(2.0, 3.0, 4.0);... do so A base class to a ref class cannot be specified as anything other than public Because you cannot supply default values for parameters as in 55 6 259 05c09.qxd:WroxPro 2/21/08 8 :55 AM Page 55 7 Chapter 9: Class Inheritance and Virtual Functions the native C++ version of the class, you define the no-arg constructor so that it initializes all three fields to 1.0 The Box class defines the Volume() function... Destructor ~CCan() { cout . CGlassBox // allowing 15% for packing 53 3 Chapter 9: Class Inheritance and Virtual Functions 259 05c09.qxd:WroxPro 2/21/08 8 :55 AM Page 53 3 virtual double Volume() const { return 0. 85* m_Length*m_Width*m_Height;. 0; // Declare a pointer to base class objects 53 5 Chapter 9: Class Inheritance and Virtual Functions 259 05c09.qxd:WroxPro 2/21/08 8 :55 AM Page 53 5 pBox = &myBox; // Set pointer to address. strcpy() #include “CandyBox.h” // For CBox and CCandyBox 52 5 Chapter 9: Class Inheritance and Virtual Functions 259 05c09.qxd:WroxPro 2/21/08 8 :54 AM Page 52 5 using std::cout; using std::endl; int main() { CCandyBox

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

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan