Before discussing the content, let's take a look at the following program, which is used for news reporting. Each news report is composed of text or images.
[Cpp]
<SPAN style = "FONT-SIZE: 18px"> class NLComponent {// used for newsletter components
Public: // abstract base class
... // Contains at least one pure virtual function
};
Class TextBlock: public NLComponent {
Public:
... // Does not contain pure virtual functions
};
Class Graphic: public NLComponent {
Public:
... // Does not contain pure virtual functions
};
Class NewsLetter {// a newsletter object
Public: // The NLComponent object.
... // Linked list Composition
Private:
List <NLComponent *> components;
};
</SPAN>
Class NLComponent {// used for newsletter components
Public: // abstract base class
... // Contains at least one pure virtual function
};
Class TextBlock: public NLComponent {
Public:
... // Does not contain pure virtual functions
};
Class Graphic: public NLComponent {
Public:
... // Does not contain pure virtual functions
};
Class NewsLetter {// a newsletter object
Public: // The NLComponent object.
... // Linked list Composition
Private:
List <NLComponent *> components;
};
Class Structure:
NewLetter is stored on the disk when it is not run, so its constructor has the istream parameter, which can read information from the stream.
Class NewsLetter {
Public:
NewsLetter (istream & str );
...
};
Pseudocode:
NewsLetter: NewsLetter (istream & str)
{
While (str ){
Reads the next component object from str;
Add the object to the linked list of the components Object of newsletter;
}
}
Or abstract the Data Reading function separately to create a readComponent, for example
[Cpp]
<SPAN style = "FONT-SIZE: 18px"> class NewsLetter {
Public:
...
Private:
// Read data from str for the next NLComponent object,
// Create a component and return a pointer.
Static NLComponent * readComponent (istream & str); // static function
...
};
NewsLetter: NewsLetter (istream & str)
{
While (str ){
// Add the pointer returned by readComponent to the end of the components linked list,
// "Push_back" is a member function of the linked list, which is used to insert at the end of the linked list.
Components. push_back (readComponent (str); // call
}
} </SPAN>
Class NewsLetter {
Public:
...
Private:
// Read data from str for the next NLComponent object,
// Create a component and return a pointer.
Static NLComponent * readComponent (istream & str); // static function
...
};
NewsLetter: NewsLetter (istream & str)
{
While (str ){
// Add the pointer returned by readComponent to the end of the components linked list,
// "Push_back" is a member function of the linked list, which is used to insert at the end of the linked list.
Components. push_back (readComponent (str); // call
}
} Here, readComponent creates a new object based on the read data. Because it can create a new object, the behavior is similar to that of the constructor, and it can create different types of objects according to the input, so we call it a virtual constructor.
A virtual constructor can create different types of objects based on the data input to it. Virtual constructor is useful in many cases. Reading object information from a disk (or through a network connection, or from a tape drive) is only one of the applications.
Another special type of virtual constructor-virtual copy constructor-is also widely used. The virtual copy constructor returns a pointer pointing to the new copy of the object that calls the function. Because of this behavior, the name of the virtual copy constructor is usually copySelf, cloneSelf, or clone. Few functions can be implemented in this direct way:
[Cpp]
<SPAN style = "FONT-SIZE: 18px"> class NLComponent {
Public:
// Declare a virtual copy constructor
Virtual NLComponent * clone () const = 0;
...
};
Class TextBlock: public NLComponent {
Public:
Virtual TextBlock * clone () const // virtual copy
{Return new TextBlock (* this);} // constructor
...
};
Class Graphic: public NLComponent {
Public:
Virtual Graphic * clone () const // virtual copy
{Return new Graphic (* this);} // constructor
...
}; </SPAN>
Class NLComponent {
Public:
// Declare a virtual copy constructor
Virtual NLComponent * clone () const = 0;
...
};
Class TextBlock: public NLComponent {
Public:
Virtual TextBlock * clone () const // virtual copy
{Return new TextBlock (* this);} // constructor
...
};
Class Graphic: public NLComponent {
Public:
Virtual Graphic * clone () const // virtual copy
{Return new Graphic (* this);} // constructor
...
};
As we can see, the virtual copy constructors of classes only call their real copy constructors. Therefore, the meaning of "copy" is the same as that of the real copy constructor. If the real copy constructor only performs a simple copy, the virtual copy constructor also performs a simple copy. If the real copy constructor performs a full copy, the virtual copy constructor also performs a full copy.
Note that the above Code implements a more relaxed virtual function return value type rule. The virtual functions that are redefined by the derived class do not have to have the same return value as the virtual functions of the base class. If the return type of a function is a pointer (or a reference) to the base class, the function of the derived class can return a pointer (or reference) to the base class ). This is not a vulnerability in the C ++ type check. It makes it possible to declare a function like a virtual constructor. This is why the clone function of TextBlock can return TextBlock * and Graphic *, even if the return type of NLComponent clone is NLComponent *.
The virtual copy constructor in NLComponent makes it easy to implement the NewLetter (normal) copy constructor:
[Cpp]
<SPAN style = "FONT-SIZE: 18px"> class NewsLetter {
Public:
NewsLetter (const NewsLetter & rhs); // copy the constructor
...
Private:
List <NLComponent *> components;
};
NewsLetter: NewsLetter (const NewsLetter & rhs)
{
// Traverse the entire rhs linked list and use the virtual copy constructor of each element
// Copy the element to the component linked list of the object.
For (list <NLComponent *>: const_iterator it =
Rhs. components. begin ();
It! = Rhs. components. end ();
++ It ){
// "It" points to the current element of rhs. components and calls the clone function of the element,
// Get a copy of the element and put the copy
// End of the component linked list of this object.
Components. push_back (* it)-> clone ());
}
} </SPAN>
Class NewsLetter {
Public:
NewsLetter (const NewsLetter & rhs); // copy the constructor
...
Private:
List <NLComponent *> components;
};
NewsLetter: NewsLetter (const NewsLetter & rhs)
{
// Traverse the entire rhs linked list and use the virtual copy constructor of each element
// Copy the element to the component linked list of the object.
For (list <NLComponent *>: const_iterator it =
Rhs. components. begin ();
It! = Rhs. components. end ();
++ It ){
// "It" points to the current element of rhs. components and calls the clone function of the element,
// Get a copy of the element and put the copy
// End of the component linked list of this object.
Components. push_back (* it)-> clone ());
}
}
The above work is to traverse the component linked list of the Copied object NewsLetter and call the virtual constructor of each element in the linked list.
We need a virtual constructor here, because the linked list contains
But we know that every pointer is actually directed to either a TextBlock object or a Graphic object. No matter who it points to, we all want to perform the correct copy operation. The virtual constructor can do this for us.
Virtualization non-member functions
Just as constructors cannot be virtual functions, non-member functions cannot be real virtual functions. However, since a function can construct new objects of different types, it is understandable that such a non-member function also exists, different features can be displayed based on the dynamic types of its parameters.
Suppose you want to implement an output operator for TextBlock and Graphic objects. The obvious method is to virtualize the output operator.
The output operator is operator <. To make the output operator A member function of the class (virtual functions supported), you must put the function parameter ostream & on the right of the operator as the right parameter.
[Cpp]
<SPAN style = "FONT-SIZE: 18px"> class NLComponent {
Public:
// Unusual Declaration of the output Operator
Virtual ostream & operator <(ostream & str) const = 0;
...
};
Class TextBlock: public NLComponent {
Public:
// Virtual output operator (also unusual)
Virtual ostream & operator <(ostream & str) const;
};
Class Graphic: public NLComponent {
Public:
// Virtual output operator (making it unusual)
Virtual ostream & operator <(ostream & str) const;
};
Although this can be done, let's look at what will happen during the call.
TextBlock t;
Graphic g;
...
T <cout;
// You need to use cout as the right operand
// Use virtual operator <
// Print t to cout.
// Unusual syntax
G <cout; // use virtual operator <
// Print g to cout.
// Unusual syntax </SPAN>
Class NLComponent {
Public:
// Unusual Declaration of the output Operator
Virtual ostream & operator <(ostream & str) const = 0;
...
};
Class TextBlock: public NLComponent {
Public:
// Virtual output operator (also unusual)
Virtual ostream & operator <(ostream & str) const;
};
Class Graphic: public NLComponent {
Public:
// Virtual output operator (making it unusual)
Virtual ostream & operator <(ostream & str) const;
};
Although this can be done, let's look at what will happen during the call.
TextBlock t;
Graphic g;
...
T <cout;
// You need to use cout as the right operand
// Use virtual operator <
// Print t to cout.
// Unusual syntax
G <cout; // use virtual operator <
// Print g to cout.
// Unusual syntax
This is contrary to the method we are familiar with, but in order to return to the normal syntax, we must remove the operator <from the TextBlock and Graphic classes, but if we do this, you can no longer declare it as virtual.
Another method is to declare a virtual function (such as print) for the print operation to define it in the TextBlock and Graphic classes. However, in this case, the syntax for printing TextBlock and Graphic objects is different from that for other types of objects using operator <as the output operator. These solutions are not satisfactory. What we want is a non-member function called operator <, which has the behavior characteristics like the print virtual function.
We define operator <and print functions to let the former call the latter!
[Cpp]
<SPAN style = "FONT-SIZE: 18px"> class NLComponent {
Public:
Virtual ostream & print (ostream & s) const = 0;
...
};
Class TextBlock: public NLComponent {
Public:
Virtual ostream & print (ostream & s) const;
...
};
Class Graphic: public NLComponent {
Public:
Virtual ostream & print (ostream & s) const;
...
};
Inline
Ostream & operator <(ostream & s, const NLComponent & c)
{
Return c. print (s );
} </SPAN>
Class NLComponent {
Public:
Virtual ostream & print (ostream & s) const = 0;
...
};
Class TextBlock: public NLComponent {
Public:
Virtual ostream & print (ostream & s) const;
...
};
Class Graphic: public NLComponent {
Public:
Virtual ostream & print (ostream & s) const;
...
};
Inline
Ostream & operator <(ostream & s, const NLComponent & c)
{
Return c. print (s );
}
Non-member functions with virtual behaviors are simple. You write a virtual function to complete the work, and then write another non-virtual function. It does not just call this virtual function. To avoid the overhead of function calls caused by this syntactic trick, you can certainly inline this non-virtual function.