Derived classes do not use new
Suppose the base class uses dynamic memory allocation:
class baseDMA { private: char * label; int rating; public: baseDMA(const char * l = "null", int r = 0); baseDMA(const baseDMA & rs); virtual ~baseDMA(); baseDMA & operator=(const baseDMA & rs); ... };
The Declaration contains special methods required by constructors when using new: destructors, copy constructors, and overloaded assignment operators.
Now, the lackDMA class is derived from baseDMA. The latter does not use new, nor does it contain other design features that are not commonly used and require special processing:
class lacksDMA : public baseDMA { private: char color[40]; public: ... };
There is no need to define the display destructor, copy constructor, and assignment operator for the lackDMA class.
Destructor: if no destructor is defined, the compiler defines a default constructor that does nothing. In fact, the default constructor of a derived class always has to do some operations: call the base class destructor after executing its own code. Because we assume that lackDMA members do not need to perform any special operations, the default destructor is appropriate.
Copy constructor: the default copy constructor performs member copy, which is inappropriate for dynamic memory allocation, but appropriate for lacksDMA members. Therefore, only inherited baseDMA objects need to be considered. Member replication will adopt the corresponding replication method according to the data type, and the long will be copied into the long by using conventional assignment. However, when copying class members or inherited class components, it is completed by using the copy constructor of the class. The default copy constructor of the lacksDMA class uses the explicit baseDMA copy constructor to copy the baseDMA part of the lacksDMA object. The default copy constructor is appropriate for lacksDMA members and for inherited baseDMA objects.
For assignment, the default assignment operator of the class will automatically use the assignment operator of the base class to assign values to the components of the base class.
The second case: the derived class uses new
Suppose the derived class uses new:
class hasDMA : public baseDMA { private: char * style; public: ... };
In this case, you must define explicit destructors, copy constructors, and assignment operators for derived classes.
The derived class destructor automatically calls the destructor of the base class, so its own responsibility is to clean up the execution of the derived class constructor. Therefore, the hasDMA destructor must release the memory managed by the pointer style and rely on the baseDMA destructor to release the memory managed by the pointer label.
baseDMA::~baseDMA() { delete []label; } hasDMA::~hasDMA() { delete []style; }
Copy constructor: the copy constructor of BaseDMA follows the general pattern for char arrays, that is, use strlen() to learn the space required to store C-style strings, allocate enough memory (number of characters plus 1 byte required to store empty characters), and use the function strcpy() to copy the original string to the destination:
baseDMA::baseDMA(const baseDMA & rs) { label = new char[std::strlen(rs.label) + 1]; std::strcpy(label, rs.label); rating = rs.rating; }
The hasDMA copy constructor can only access hasDMA data, so it must call the baseDMA copy constructor to process shared baseDMA data:
hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) { style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); }
The member initialization list passes a hasDMA reference to the baseDMA constructor. There is no baseDMA constructor with parameter type hasDMA reference, and such a constructor is not required. Because the copy constructor baseDMA has a baseDMA reference parameter, and the base class reference can point to a derived type. The baseDMA copy constructor will use the baseDMA part of the hasDMA parameter to construct the baseDMA part of the new object.
Assignment operator, BaseDMA assignment operator follows the general pattern:
baseDMA & baseDMA::operator=(const baseDMA & rs) { if (this == &rs) return *this; delete[]label; label = new char[std::strlen(rs.label) + 1]; std::strcpy(label, rs.label); rating = rs.rating; return *this; }
Since hasDMA also uses dynamic memory allocation, it also requires an explicit assignment operator. As a method of hasDMA, it can only access the data of hasDMA. However, the explicit assignment operator of the derived class must be responsible for the assignment of all inherited baseDMA base class objects. This can be done by explicitly calling the base class assignment operator:
hasDMA & hasDMA::operator=(const hasDMA & hs) { if (this == &hs) return *this; baseDMA::operator=(hs); delete[]style; style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); return *this; }
The following statement is a little strange:
baseDMA::operator=(hs);
However, by using function notation instead of operator notation, you can use scope resolution operators. In fact, the meaning of the sentence is as follows:
*this = hs; //use baseDMA::operator=()
When using the following code, the compiler will use hasDMA::operator = () to form a recursive call. Use function notation so that the assignment operator is called correctly.
When both the base class and the derived class adopt dynamic memory allocation, the destructor, copy constructor and assignment operator of the derived class must use the corresponding base class methods to process the base class elements.
For destructors, this is done automatically. For constructors, this is done by calling the copy constructor of the base class in the initialization member list. If you do not, the default constructor of the base class will be called automatically.
For assignment operators. This is done by explicitly calling the assignment operator of the base class using the scope resolution operator.
dma.h
#ifndef DMA_H_ #define DMA_H_ #include <iostream> //Base class class baseDMA { private: char * label; int rating; public: baseDMA(const char * l = "null", int r = 0); baseDMA(const baseDMA & rs); virtual ~baseDMA(); baseDMA & operator=(const baseDMA & rs); friend std::ostream & operator<<(std::ostream & os, const baseDMA & rs); }; //lacksDMA class lacksDMA : public baseDMA { private: enum { COL_LEN = 40 }; char color[COL_LEN]; public: lacksDMA(const char * c = "blank", const char * l = "null", int r = 0); lacksDMA(const char * c, const baseDMA & rs); friend std::ostream & operator<<(std::ostream & os, const lacksDMA & rs); }; //hasDMA class hasDMA : public baseDMA { private: char * style; public: hasDMA(const char * s = "none", const char * l = "null", int r = 0); hasDMA(const char * s, const baseDMA & rs); hasDMA(const hasDMA & hs); ~hasDMA(); hasDMA & operator=(const hasDMA & rs); friend std::ostream & operator<<(std::ostream & os, const hasDMA & rs); }; #endif
dma.cpp
#include "dma.h" #include <cstring> #include <string.h> //baseDMA baseDMA::baseDMA(const char * l, int r) { label = new char[std::strlen(l) + 1]; std::strcpy(label, l); rating = r; } baseDMA::baseDMA(const baseDMA & rs) { label = new char[std::strlen(rs.label) + 1]; std::strcpy(label, rs.label); rating = rs.rating; } baseDMA::~baseDMA() { delete []label; } baseDMA & baseDMA::operator=(const baseDMA & rs) { if (this == &rs) return *this; delete[]label; label = new char[std::strlen(rs.label) + 1]; std::strcpy(label, rs.label); rating = rs.rating; return *this; } std::ostream & operator<<(std::ostream & os, const baseDMA & rs) { os << "Label: " << rs.label << std::endl; os << "Rating: " << rs.rating << std::endl; return os; } //lacksDMA lacksDMA::lacksDMA(const char * c, const char * l, int r) : baseDMA(l, r) { std::strncpy(color, c, 39); color[39] = '\0'; } lacksDMA::lacksDMA(const char * c, const baseDMA & rs) : baseDMA(rs) { std::strncpy(color, c, COL_LEN - 1); color[COL_LEN - 1] = '\0'; } std::ostream & operator<<(std::ostream & os, const lacksDMA & ls) { os << (const baseDMA &)ls; os << "Color: " << ls.color << std::endl; return os; } //hasDMA hasDMA::hasDMA(const char * s, const char * l, int r) : baseDMA(l, r) { style = new char[std::strlen(s) + 1]; std::strcpy(style, s); } hasDMA::hasDMA(const char * s, const baseDMA & rs) : baseDMA(rs) { style = new char[std::strlen(s) + 1]; std::strcpy(style, s); } hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) { style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); } hasDMA::~hasDMA() { delete []style; } hasDMA & hasDMA::operator=(const hasDMA & hs) { if (this == &hs) return *this; baseDMA::operator=(hs); delete[]style; style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); return *this; } std::ostream & operator<<(std::ostream & os, const hasDMA & hs) { os << (const baseDMA &)hs; os << "Style: " << hs.style << std::endl; return os; }
How derived classes use friends of the base class:
friend std::ostream & operator<<(std::ostream & os, const hasDMA & rs);
As a friend of the hasDMA class, this function can access the style member. However, there is still a problem: if the function is not a friend of the baseDMA class, how does it access the members label and rating? Use the baseDMA class friend function operator < < (). Because a friend is not a member function, you cannot use the scope resolution operator to indicate which function to use? Use cast to select the correct function when matching the prototype. Therefore, the code converts the parameter const hasDMA & to the parameter of const baseDMA & Type:
std::ostream & operator<<(std::ostream & os, const hasDMA & hs) { os << (const baseDMA &)hs; os << "Style: " << hs.style << std::endl; return os; }
main.cpp
#include <iostream> #include "dma.h" int main() { using std::cout; using std::endl; baseDMA shirt("Portabelly", 8); lacksDMA balloon("red", "Blimpo", 4); hasDMA map("Mercator", "Buffalo Keys", 5); cout << "Displaying baseDMA object:\n"; cout << shirt << endl; cout << "Displaying lacksDMA object:\n"; cout << balloon << endl; cout << "Displaying hasDMA object:\n"; cout << map << endl; lacksDMA balloon2(balloon); cout << "Result of lacksDMA copy:\n"; cout << balloon2 << endl; hasDMA map2; map2 = map; cout << "Result of hasDMA assignment:\n"; cout << map2 << endl; return 0; }