Depending on the different input or dynamic types of function parameters, “virtualization” is a useful technique to construct new objects of different types accordingly, or to conceive of non-member functions whose behavior changes accordingly.
Constructors and non-member functions can’t really be virtual. We just make them act “virtually” so that it is easier to use. The term “virtual” means that a function will achieve type-specific behavior when we have a pointer or reference to an object without knowing its dynamic type in advance.
Suppose we write applications for working with newsletters, where a newsletter consists of components that are either textual or graphical:
The classes relate in this way:
NewsLetter object ┌───────────────┐ │ ... │ ├───────────────┤ ┌──────────────┐ │[list object]--│-----pointers------>│ NLComponent │ └───────────────┘ └──────────────┘ public inheritance ↗ ↖ public inheritance ┌───────────┐ ┌──────────┐ │ TextBlock │ │ Graphic │ └───────────┘ └──────────┘
NewsLetter objects are stored on disk, it is convevient that
NewsLetter takes an
istream to read information from the stream as it creates the necessary in-core data structures. Depending on the data it reads, we need to create either a
TextBlock or a
Graphic, which are different types of objects. Here comes the
readComponent, which acts like constructor for its creating new objects, while it is also able to create different types of objects according to the iput it is given. Thus we call such a constructor as the virtual constructor, which are useful in many constexts.
readComponent acting as a virtual constructor, it is easy to implement the constructor for
Among all kinds of virtual functions, there is another widely useful one: the virtual copy constructor, which returns a pointer to a new copy of the object invoking the function, and is typically named like
cloneSelf, or simply
clone. These virtual copy constructors just calls its real copy constructor, so that the meaning of “copy” keeps the same for both functions1 - consistency.
Notice that a derived class’s redefinition of a base class’s virtual function declare different return types here: if the function’s return type is a pointer (or a reference) to a base class, the derived class’s function may return a pointer (or reference) to a class derived from that base class. Thus
clone returns a
clone returns a
Graphic* while the return type of
Taking advantage of the virtual copy constructor in
NewsLetter only need to implement a normal copy constructor:
Virtual Non-member functions
Suppose we’d like to implement output operators for the
Graphic classes. Given that the defaultly output operator
operator<< takes an
ostream& as its left-hand argument, we can not make it a member function of the
Graphic classes, so it can’t be declared as
virtual. On the other hand, if we insist on declaring a virtual function for printing (e.g.,
Graphic, the syntax for printing
Graphic objects is inconsistent with that for the other types in the language, which makes our clients unhappy.
What we want it a non-member function called
operator<< that exhibits the behavior of a virtual function like
Since the non-virtual function does nothing but call the virtual function that does the real work, we inline the non-virtual function to avoid the cost of a function call.
Note that, although not easy, it is possible to make non-member functions act virtually on more than one of their arguments - details in MECpp item 31.
If the real copy constructor performs a deep copy, so does the virtual copy constructor. If the real copy constructor does something fancy like reference counting or copy-on-write (MECpp item 29), so does the virtual copy constructor. ↩︎