Multiple Inheritance
The use of more than one immediate base class is usually called multiple inheritance. In contrast, having just one direct base class is called single inheritance.
The implementation of this clearly involves some (simple) compiler technique to ensure that functions do functions according to the correct base class. Virtual functions work as usual.
Ambiguity Resolution
Two base classes may have member functions with the same name. these functions must be disambiguated using the scope resolution operation i.e. Base::Function() when we want to use the function.
explicit disambiguation is messy, so it is usually best to resolve such problems by defining a new function in the derived class and using the explicit disambiguation inside that.
Inheritance and Using Declarations
Overload resolution is not applied across different class scopes. In particular, ambiguities between functions from different base classes are not resolved based on argument types.
What if the use of the same name in different base classes was the result of a deliberate design decision and the user wanted selection based on the argument types? In that case, a using declaration can bring the functions into a common scope.
c l a s s A B : p u b l i c A , p u b l i c B
{
p u b l i c :
u s i n g A :: f ;
u s i n g B :: f ;
c h a r f (c h a r ); // hides A::f(char)
A B f (A B );
};
Using declarations allow a programmer to compose a set of overloaded functions from base classes and the derived class. Functions declared in the derived class hide functions that would otherwise be available from a base. Virtual functions from bases can be overridden as ever.
A using declaration in a class definition must refer to members of a base class. A using declaration may not be used for a member of a class from outside that class, its derived classes, and their member functions.
A using directive may not appear in a class definition and may not be used for a class. A using declaration cannot be used to gain access to additional information. It is simply a mechanism for making accessible information more convenient to use.
Replicated Base Classes
With the ability of specifying more than one base class comes the possibility of having a class as a base twice.
Examples of where the common base class shouldn’t be represented by two separate objects can be handled using a virtual base class
Usually, a base class that is replicated the way Link is here is an implementation detail that shouldn’t be used from outside its immediate derived class. If such a base must be referred to from a point where more than one copy of the base is visible, the reference must be explicitly qualified to resolve the ambiguity.
p->Task::Link::next = 0; / / ok
p->Displayed::Link::next =0; / / ok
Overriding
A virtual function of a replicated base class can be overridden by a (single) function in a derived class. Typically, an overriding function calls its base class versions and then does the work specific to the derived class:
void Radio::write()
{
Transmitter::write() ;
Receiver::write() ;
// write radio specific information
}
Virtual Base Classes
In an inheritance graph, every base class of a given name that is specified to be virtual will be represented by a single object of that class. On the other hand, each base class not specified virtual will have its own sub object representing it.
Programming Virtual Bases
When defining the functions for a class with a virtual base, the programmer in general cannot know whether the base will be shared with other derived classes. This can be a problem when implementing a service that requires a base class function to be called exactly once. For example, the language ensures that a constructor of a virtual base is called exactly once. The constructor of a virtual base is invoked (implicitly or explicitly) from the constructor for the complete object (the constructor for the most derived class).
The constructor for a virtual base is called before the constructors for its derived classes. Where needed, the programmer can simulate this scheme by calling a virtual base class function only from the most derived class.
Using Multiple Inheritance
The simplest and most obvious use of multiple inheritance is to ‘‘glue’’ two otherwise unrelated classes together as part of the implementation of a third class. This use of multiple inheritance is crude, effective, and important, but not very interesting. Basically, it saves the programmer from writing a lot of forwarding functions. This technique does not affect the overall design of a program significantly and can occasionally clash with the wish to keep implementation details hidden. However, a technique doesn’t have to be clever to be useful.
Using multiple inheritance to provide implementations for abstract classes is more fundamental in that it affects the way a program is designed.
Multiple inheritance allows sibling classes to share information without introducing a dependence on a unique common base class in a program. This is the case in which the so called diamond shaped inheritance occurs. A virtual base class, as opposed to an ordinary base class, is needed if the base class cannot be replicated.
If we take this idea to its logical conclusion, all of the derivations from the abstract classes that constitute our application’s interfaces would become virtual. This does indeed seem to be the most logical, general, and flexible approach. The reason I didn’t do that was partly historical and partly because the most obvious and common techniques for implementing virtual bases impose time and space overhead that make their extensive use within a class unattractive.
Overriding Virtual Base Functions
A derived class can override a virtual function of its direct or indirect virtual base class. In particular, two different classes might override different virtual functions from the virtual base. In that way, several derived classes can contribute implementations to the interface presented by a virtual base class.
What if different derived classes override the same function? This is allowed if and only if some overriding class is derived from every other class that overrides the function. That is, one function must override all others.
If two classes override a base class function, but neither overrides the other, the class hierarchy is an error. No virtual function table can be constructed because a call to that function on the complete object would have been ambiguous.
A class that provides some – but not all – of the implementation for a virtual base class is often called a ‘‘mixin.’’
Access Control
A member of a class can be private, protected, or public:
– If it is private, its name can be used only by member functions and friends of the class in which it is declared.
– If it is protected, its name can be used only by member functions and friends of the class in which it is declared and by member functions and friends of classes derived from this class.
– If it is public, its name can be used by any function.
In a class, a member is by default private; in a struct, a member is by default public.
Use of Protected Members
The private/public model allows the programmer to distinguish clearly between the implementers and the general public, but it does not provide a way of catering specifically to derived classes.
Members declared protected are far more open to abuse than members declared private. In particular, declaring data members protected is usually a design error. Placing significant amounts of data in a common class for all derived classes to use leaves that data open to corruption. Worse, protected data, like public data, cannot easily be restructured because there is no good way of finding every use. Thus, protected data becomes a software maintenance problem.
Access to Base Classes
Like a member, a base class can be declared private, protected, or public. For example:
class X : public B{ /* ... */ };
class Y : protected B{ /* ... */ };
class Z : private B{ /* ... */ };
Public derivation makes the derived class a subtype of its base; this is the most common form of derivation. Protected and private derivation are used to represent implementation details. Protected bases are useful in class hierarchies in which further derivation is the norm;
The access specifier for a base class can be left out. In that case, the base defaults to a private base for a class and a public base for a struct.
The access specifier for a base class controls the access to members of the base class and the conversion of pointers and references from the derived class type to the base class type. Consider a class D derived from a base class B:
– If B is a private base, its public and protected members can be used only by member functions and friends of D. Only friends and members of D can convert a D* to a B*.
– If B is a protected base, its public and protected members can be used only by member functions and friends of D and by member functions and friends of classes derived from D. Only friends and members of D and friends and members of classes derived from D can convert a D* to a B*.
– If B is a public base, its public members can be used by any function. In addition, its protected members can be used by members and friends of D and members and friends of classes derived from D. Any function can convert a D* to a B*.
Multiple Inheritance and Access Control
If the inheritance is virtual there should be no ambiguity.
If it is not virtual there will be ambiguity for non static members.
Using Declarations and Access Control
A using declaration cannot be used to gain access to additional information. It is simply a mechanism for making accessible information more convenient to use. On the other hand, once access is available, it can be granted to other users.
class B {
private:
int a;
protected:
int b;
public:
int c;
};
class D : public B {
public:
using B: :a; // error: B::a is private
using B: :b; // make B::b publically available through D
};
Run Time Type Information
Recovering the ‘‘lost’’ type of an object requires us to somehow ask the object to reveal its type. Any operation on an object requires us to have a pointer or reference of a suitable type for the object. Consequently, the most obvious and useful operation for inspecting the type of an object at run time is a type conversion operation that returns a valid pointer if the object is of the expected type and a null pointer if it isn’t. The dynamiccast operator does exactly that.
if (Ivalbox* pb = dynamiccast(pw)) / / does pw point to an Ivalbox?
pb>
dosomething() ;
else {
// Oops! unexpected event
}
One way of explaining what is going on is that dynamiccast translates from the implementation oriented language to the language of the application.
It is neither necessary nor desirable to make the actual type of the object explicit in this interaction between ‘‘the system’’ and the application. An interface exists to represent the essentials of an interaction.
The use of type information at run time is conventionally referred to as ‘‘runtime type information’’ and often abbreviated to RTTI.
Casting from a base class to a derived class is often called a downcast because of the convention of drawing inheritance trees growing from the root down. Similarly, a cast from a derived class to a base is called an upcast. A cast that goes from a base to a sibling class is called a crosscast.
Dynamic_cast
The dynamiccast operator takes two operands, a type bracketed by < and >, and a pointer or reference bracketed by ( and ). Consider first the pointer case:
dynamiccast(p)
If p is of type T* or an accessible base class of T, the result is exactly as if we had simply assigned p to a T*.
it is reassuring to know that dynamiccast doesn’t allow accidental violation of the protection of private and protected base classes.
The purpose of dynamiccast is to deal with the case in which the correctness of the conversion cannot be determined by the compiler. In that case,
dynamiccast(p)
looks at the object pointed to by p (if any). If that object is of class T or has a unique base class of type T, then dynamiccast returns a pointer of type T* to that object; otherwise, 0 is returned. If the value of p is 0, dynamiccast(p)returns 0. Note the requirement that the conversion must be to a uniquely identified object. It is possible to construct examples where the conversion fails and 0 is returned because the object pointed to by p has more than one sub object representing bases of type T.
A dynamiccast requires a pointer or a reference to a polymorphic type to do a downcast or a crosscast.
Requiring the pointer’s type to be polymorphic simplifies the implementation of dynamiccast because it makes it easy to find a place to hold the necessary information about the object’s type. A typical implementation will attach a ‘‘type information object’’ to an object by placing a pointer to the type information in the object’s virtual function table
Restricting dynamiccast to polymorphic types also makes sense from a logical point of view. This is, if an object has no virtual functions, it cannot safely be manipulated without knowledge of its exact type. Consequently, care should be taken not to get such an object into a context in which its type isn’t known. If its type is known, we don’t need to use dynamiccast.
A dynamiccast to void* can be used to determine the address of the beginning of an object of polymorphic type.
Dynamic_cast of References
To get polymorphic behavior, an object must be manipulated through a pointer or a reference. When a dynamiccast is used for a pointer type, a 0 indicates failure. That is neither feasible nor desirable for references.
Given a pointer result, we must consider the possibility that the result is 0; that is, that the pointer doesn’t point to an object. Consequently, the result of a dynamiccast of a pointer should always be explicitly tested. For a pointer p, dynamiccast(p) can be seen as the question, ‘‘Is the object pointed to by p of type T?’’
On the other hand, we may legitimately assume that a reference refers to an object. Consequently, dynamiccast(r) of a reference r is not a question but an assertion: ‘‘The object referred to by r is of type T.’’ The result of a dynamiccast for a reference is implicitly tested by the implementation of dynamiccast itself. If the operand of a dynamiccast to a reference isn’t of the expected type, a badcast exception is thrown.
Navigating Class Hierarchies
When only single inheritance is used, a class and its base classes constitute a tree rooted in a single base class. This is simple but often constraining. When multiple inheritance is used, there is no single root. This in itself doesn’t complicate matters much. However, if a class appears more than once in a hierarchy, we must be a bit careful when we refer to the object or objects that represent that class.
Naturally, we try to keep hierarchies as simple as our application allows (and no simpler). However, once a nontrivial hierarchy has been made we soon need to navigate it to find an appropriate class to use as an interface. This need occurs in two variants. That is,
• Sometimes, we want to explicitly name an object of a base class or a member of a base class.
• At other times, we want to get a pointer to the object representing a base or derived class of an object given a pointer to a complete object or some sub object.
Static and Dynamic Casts
A dynamiccast can cast from a polymorphic virtual base class to a derived class or a sibling class. A staticcast does not examine the object it casts from, so it cannot.
The dynamiccast requires a polymorphic operand because there is no information stored in a non polymorphic object that can be used to find the objects for which it represents a base. For objects of such types, only static type information will be available. However, the information needed to provide runtime type identification includes the information needed to implement the dynamiccast.
Why would anyone want to use a staticcast for class hierarchy navigation? There is a small runtime cost associated with the use of a dynamiccast. More significantly, there are millions of lines of code that were written before dynamiccast became available. This code relies on alternative ways of making sure that a cast is valid, so the checking done by dynamiccast is seen as redundant. However, such code is typically written using the Cstyle cast; often obscure errors remain. Where possible, use the safer dynamiccast. The compiler cannot assume anything about the memory pointed to by a void*. This implies that dynamiccast – which must look into an object to determine its type – cannot cast from a void*. For that, a staticcast is needed.
It is not possible to cast to a private base class, and ‘‘casting away const’’ requires a constcast. Even then, using the result is safe only provided the object wasn’t originally declared const.
Class Object Construction and Destruction
A class object is more than simply a region of memory. A class object is built from ‘‘raw memory’’ by its constructors and it reverts to ‘‘raw memory’’ as its destructors are executed. Construction is bottom up, destruction is top down, and a class object is an object to the extent that it has been constructed or destroyed. This is reflected in the rules for RTTI, exception handling, and virtual functions.
It is extremely unwise to rely on details of the order of construction and destruction, but that order can be observed by calling virtual functions, dynamiccast, or typeid at a point where the object isn’t complete.
At the point of construction, the object isn’t is merely a partially constructed object. It is best to avoid calling virtual functions during construction and destruction.
Typeid and Extended Type Information
The dynamiccast operator serves most needs for information about the type of an object at run time. Importantly, it ensures that code written using it works correctly with classes derived from those explicitly mentioned by the programmer. Thus, dynamiccast preserves flexibility and extensibility in a manner similar to virtual functions.
However, it is occasionally essential to know the exact type of an object. For example, we might like to know the name of the object’s class or its layout. The typeid operator serves this purpose by yielding an object representing the type of its operand.
const typeinfo& typeid(typename) throw(badtypeid) ; / / pseudo declaration
A typeid() is most commonly used to find the type of an object referred to by a reference or a pointer
typeid(r) ; / / type of object referred to by r
typeid(*p) ; / / type of object pointed to by p
typeid(p) ; / / type of pointer, that is, Shape* (uncommon, except as a mistake)
If the value of a pointer or a reference operand is 0, typeid() throws a badtypeid exception.
It is not guaranteed that there is only one typeinfo object for each type in the system. In fact, where dynamically linked libraries are used it can be hard for an implementation to avoid duplicate typeinfo objects. Consequently, we should use == on typeinfo objects to test equality, rather than == on pointers to such objects.
We sometimes want to know the exact type of an object so as to perform some standard service on the whole object (and not just on some base of the object). Ideally, such services are presented as virtual functions so that the exact type needn’t be known.
The character representation of a class’ name is implementation defined. This Cstyle string resides in memory owned by the system, so the programmer should not attempt to delete[] it.
Uses and Misuses of RTTI
One should use explicit runtime type information only when necessary. Static (compiletime) checking is safer, implies less overhead, and – where applicable – leads to better structured programs.
Using dynamiccast rather than typeid would improve this code only marginally.
Many examples of proper use of RTTI arise when some service code is expressed in terms of one class and a user wants to add functionality through derivation.
Even if the user is willing to modify the base classes, such modification may cause its own problems. For example, it may be necessary to introduce dummy implementations of virtual functions in classes for which those functions are not needed or not meaningful.
Pointers to Members
C++ offers a facility for indirectly referring to a member of a class. A pointer to a member is a value that identifies a member of a class. You can think of it as the position of the member in an object of the class, but of course an implementation takes into account the differences between data members, virtual functions, non virtual functions, etc.
Pstdmem s = &Stdinterface::suspend;
(p->*s)() ; // call through pointer to member
A pointer to member can be obtained by applying the address of operator & to a fully qualified class member name, for example, &Stdinterface::suspend. A variable of type ‘‘pointer to member of class X’’ is declared using a declarator of the form X::*. The use of typedef to compensate for the lack of readability of the C declarator syntax is typical. However, please note how the X::* declarator matches the traditional * declarator exactly.
A pointer to member m can be used in combination with an object. The operators ->* and .* allow the programmer to express such combinations. For example, p->* m binds m to the object pointed to by p, and obj.*m binds m to the object obj. The result can be used in accordance with m’s type. It is not possible to store the result of a ->* or a .* operation for later use.
Naturally, if we knew which member we wanted to call we would invoke it directly rather than mess with pointers to members. Just like ordinary pointers to functions, pointers to member functions are used when we need to refer to a function without having to know its name. However, a pointer to member isn’t a pointer to a piece of memory the way a pointer to a variable or a pointer to a function is. It is more like an offset into a structure or an index into an array. When a pointer to member is combined with a pointer to an object of the right type, it yields something that identifies a particular member of a particular object.
Base and Derived Classes
A derived class has at least the members that it inherits from its base classes. Often it has more. This implies that we can safely assign a pointer to a member of a base class to a pointer to a member of a derived class, but not the other way around. This property is often called contravariance.
This contravariance rule appears to be the opposite of the rule that says we can assign a pointer to a derived class to a pointer to its base class. In fact, both rules exist to preserve the fundamental guarantee that a pointer may never point to an object that doesn’t at least have the properties that the pointer promises.
Free Store
It is possible to take over memory management for a class by defining operator new()and operator delete().
Member operator new()s and operator delete()s are implicitly static members. Consequently, they don’t have a this pointer and do not modify an object. They provide storage that a constructor can initialize and a destructor can clean up.
void* operator new(sizet) ;
void operator delete(void*, sizet) ;
if you want to supply an allocator/deallocator pair that works correctly for derived classes, you must either supply a virtual destructor in the base class or refrain from using the sizet argument in the deallocator.
Array Allocation
The operator new() and operator delete() functions allow a user to take over allocation and deallocation of individual objects; operator new[]() and operator delete[]() serve exactly the same role for the allocation and deallocation of arrays.
“Virtual Constructors”
To construct an object, a constructor needs the exact type of the object it is to create. Consequently, a constructor cannot be virtual. Furthermore, a constructor is not quite an ordinary function. In particular, it interacts with memory management routines in ways ordinary member functions don’t. Consequently, you cannot have a pointer to a constructor.
Both of these restrictions can be circumvented by defining a function that calls a constructor and returns a constructed object. This is fortunate because creating a new object without knowing its exact type is often useful.
The type of an overriding function must be the same as the type of the virtual function it overrides, except that the return type may be relaxed. That is, if the original return type was B*, then the return type of the overriding function may be D*, provided B is a public base of D. Similarly, a return type of B& may be relaxed to D&.
Advice
[1] Use ordinary multiple inheritance to express a union of features.
[2] Use multiple inheritance to separate implementation details from an interface.
[3] Use a virtual base to represent something common to some, but not all, classes in a hierarchy.
[4] Avoid explicit type conversion (casts).
[5] Use dynamiccast where class hierarchy navigation is unavoidable.
[6] Prefer dynamiccast overtypeid.
[7] Prefer private to protected.
[8] Don’t declare data members protected.
[9] If a class defines operator delete(), it should have a virtual destructor.
[10] Don’t call virtual functions during construction or destruction.
[11] Use explicit qualification for resolution of member names sparingly and preferably use it in overriding functions
The use of more than one immediate base class is usually called multiple inheritance. In contrast, having just one direct base class is called single inheritance.
The implementation of this clearly involves some (simple) compiler technique to ensure that functions do functions according to the correct base class. Virtual functions work as usual.
Ambiguity Resolution
Two base classes may have member functions with the same name. these functions must be disambiguated using the scope resolution operation i.e. Base::Function() when we want to use the function.
explicit disambiguation is messy, so it is usually best to resolve such problems by defining a new function in the derived class and using the explicit disambiguation inside that.
Inheritance and Using Declarations
Overload resolution is not applied across different class scopes. In particular, ambiguities between functions from different base classes are not resolved based on argument types.
What if the use of the same name in different base classes was the result of a deliberate design decision and the user wanted selection based on the argument types? In that case, a using declaration can bring the functions into a common scope.
c l a s s A B : p u b l i c A , p u b l i c B
{
p u b l i c :
u s i n g A :: f ;
u s i n g B :: f ;
c h a r f (c h a r ); // hides A::f(char)
A B f (A B );
};
Using declarations allow a programmer to compose a set of overloaded functions from base classes and the derived class. Functions declared in the derived class hide functions that would otherwise be available from a base. Virtual functions from bases can be overridden as ever.
A using declaration in a class definition must refer to members of a base class. A using declaration may not be used for a member of a class from outside that class, its derived classes, and their member functions.
A using directive may not appear in a class definition and may not be used for a class. A using declaration cannot be used to gain access to additional information. It is simply a mechanism for making accessible information more convenient to use.
Replicated Base Classes
With the ability of specifying more than one base class comes the possibility of having a class as a base twice.
Examples of where the common base class shouldn’t be represented by two separate objects can be handled using a virtual base class
Usually, a base class that is replicated the way Link is here is an implementation detail that shouldn’t be used from outside its immediate derived class. If such a base must be referred to from a point where more than one copy of the base is visible, the reference must be explicitly qualified to resolve the ambiguity.
p->Task::Link::next = 0; / / ok
p->Displayed::Link::next =0; / / ok
Overriding
A virtual function of a replicated base class can be overridden by a (single) function in a derived class. Typically, an overriding function calls its base class versions and then does the work specific to the derived class:
void Radio::write()
{
Transmitter::write() ;
Receiver::write() ;
// write radio specific information
}
Virtual Base Classes
In an inheritance graph, every base class of a given name that is specified to be virtual will be represented by a single object of that class. On the other hand, each base class not specified virtual will have its own sub object representing it.
Programming Virtual Bases
When defining the functions for a class with a virtual base, the programmer in general cannot know whether the base will be shared with other derived classes. This can be a problem when implementing a service that requires a base class function to be called exactly once. For example, the language ensures that a constructor of a virtual base is called exactly once. The constructor of a virtual base is invoked (implicitly or explicitly) from the constructor for the complete object (the constructor for the most derived class).
The constructor for a virtual base is called before the constructors for its derived classes. Where needed, the programmer can simulate this scheme by calling a virtual base class function only from the most derived class.
Using Multiple Inheritance
The simplest and most obvious use of multiple inheritance is to ‘‘glue’’ two otherwise unrelated classes together as part of the implementation of a third class. This use of multiple inheritance is crude, effective, and important, but not very interesting. Basically, it saves the programmer from writing a lot of forwarding functions. This technique does not affect the overall design of a program significantly and can occasionally clash with the wish to keep implementation details hidden. However, a technique doesn’t have to be clever to be useful.
Using multiple inheritance to provide implementations for abstract classes is more fundamental in that it affects the way a program is designed.
Multiple inheritance allows sibling classes to share information without introducing a dependence on a unique common base class in a program. This is the case in which the so called diamond shaped inheritance occurs. A virtual base class, as opposed to an ordinary base class, is needed if the base class cannot be replicated.
If we take this idea to its logical conclusion, all of the derivations from the abstract classes that constitute our application’s interfaces would become virtual. This does indeed seem to be the most logical, general, and flexible approach. The reason I didn’t do that was partly historical and partly because the most obvious and common techniques for implementing virtual bases impose time and space overhead that make their extensive use within a class unattractive.
Overriding Virtual Base Functions
A derived class can override a virtual function of its direct or indirect virtual base class. In particular, two different classes might override different virtual functions from the virtual base. In that way, several derived classes can contribute implementations to the interface presented by a virtual base class.
What if different derived classes override the same function? This is allowed if and only if some overriding class is derived from every other class that overrides the function. That is, one function must override all others.
If two classes override a base class function, but neither overrides the other, the class hierarchy is an error. No virtual function table can be constructed because a call to that function on the complete object would have been ambiguous.
A class that provides some – but not all – of the implementation for a virtual base class is often called a ‘‘mixin.’’
Access Control
A member of a class can be private, protected, or public:
– If it is private, its name can be used only by member functions and friends of the class in which it is declared.
– If it is protected, its name can be used only by member functions and friends of the class in which it is declared and by member functions and friends of classes derived from this class.
– If it is public, its name can be used by any function.
In a class, a member is by default private; in a struct, a member is by default public.
Use of Protected Members
The private/public model allows the programmer to distinguish clearly between the implementers and the general public, but it does not provide a way of catering specifically to derived classes.
Members declared protected are far more open to abuse than members declared private. In particular, declaring data members protected is usually a design error. Placing significant amounts of data in a common class for all derived classes to use leaves that data open to corruption. Worse, protected data, like public data, cannot easily be restructured because there is no good way of finding every use. Thus, protected data becomes a software maintenance problem.
Access to Base Classes
Like a member, a base class can be declared private, protected, or public. For example:
class X : public B{ /* ... */ };
class Y : protected B{ /* ... */ };
class Z : private B{ /* ... */ };
Public derivation makes the derived class a subtype of its base; this is the most common form of derivation. Protected and private derivation are used to represent implementation details. Protected bases are useful in class hierarchies in which further derivation is the norm;
The access specifier for a base class can be left out. In that case, the base defaults to a private base for a class and a public base for a struct.
The access specifier for a base class controls the access to members of the base class and the conversion of pointers and references from the derived class type to the base class type. Consider a class D derived from a base class B:
– If B is a private base, its public and protected members can be used only by member functions and friends of D. Only friends and members of D can convert a D* to a B*.
– If B is a protected base, its public and protected members can be used only by member functions and friends of D and by member functions and friends of classes derived from D. Only friends and members of D and friends and members of classes derived from D can convert a D* to a B*.
– If B is a public base, its public members can be used by any function. In addition, its protected members can be used by members and friends of D and members and friends of classes derived from D. Any function can convert a D* to a B*.
Multiple Inheritance and Access Control
If the inheritance is virtual there should be no ambiguity.
If it is not virtual there will be ambiguity for non static members.
Using Declarations and Access Control
A using declaration cannot be used to gain access to additional information. It is simply a mechanism for making accessible information more convenient to use. On the other hand, once access is available, it can be granted to other users.
class B {
private:
int a;
protected:
int b;
public:
int c;
};
class D : public B {
public:
using B: :a; // error: B::a is private
using B: :b; // make B::b publically available through D
};
Run Time Type Information
Recovering the ‘‘lost’’ type of an object requires us to somehow ask the object to reveal its type. Any operation on an object requires us to have a pointer or reference of a suitable type for the object. Consequently, the most obvious and useful operation for inspecting the type of an object at run time is a type conversion operation that returns a valid pointer if the object is of the expected type and a null pointer if it isn’t. The dynamiccast operator does exactly that.
if (Ivalbox* pb = dynamiccast
pb>
dosomething() ;
else {
// Oops! unexpected event
}
One way of explaining what is going on is that dynamiccast translates from the implementation oriented language to the language of the application.
It is neither necessary nor desirable to make the actual type of the object explicit in this interaction between ‘‘the system’’ and the application. An interface exists to represent the essentials of an interaction.
The use of type information at run time is conventionally referred to as ‘‘runtime type information’’ and often abbreviated to RTTI.
Casting from a base class to a derived class is often called a downcast because of the convention of drawing inheritance trees growing from the root down. Similarly, a cast from a derived class to a base is called an upcast. A cast that goes from a base to a sibling class is called a crosscast.
Dynamic_cast
The dynamiccast operator takes two operands, a type bracketed by < and >, and a pointer or reference bracketed by ( and ). Consider first the pointer case:
dynamiccast
If p is of type T* or an accessible base class of T, the result is exactly as if we had simply assigned p to a T*.
it is reassuring to know that dynamiccast doesn’t allow accidental violation of the protection of private and protected base classes.
The purpose of dynamiccast is to deal with the case in which the correctness of the conversion cannot be determined by the compiler. In that case,
dynamiccast
looks at the object pointed to by p (if any). If that object is of class T or has a unique base class of type T, then dynamiccast returns a pointer of type T* to that object; otherwise, 0 is returned. If the value of p is 0, dynamiccast
A dynamiccast requires a pointer or a reference to a polymorphic type to do a downcast or a crosscast.
Requiring the pointer’s type to be polymorphic simplifies the implementation of dynamiccast because it makes it easy to find a place to hold the necessary information about the object’s type. A typical implementation will attach a ‘‘type information object’’ to an object by placing a pointer to the type information in the object’s virtual function table
Restricting dynamiccast to polymorphic types also makes sense from a logical point of view. This is, if an object has no virtual functions, it cannot safely be manipulated without knowledge of its exact type. Consequently, care should be taken not to get such an object into a context in which its type isn’t known. If its type is known, we don’t need to use dynamiccast.
A dynamiccast to void* can be used to determine the address of the beginning of an object of polymorphic type.
Dynamic_cast of References
To get polymorphic behavior, an object must be manipulated through a pointer or a reference. When a dynamiccast is used for a pointer type, a 0 indicates failure. That is neither feasible nor desirable for references.
Given a pointer result, we must consider the possibility that the result is 0; that is, that the pointer doesn’t point to an object. Consequently, the result of a dynamiccast of a pointer should always be explicitly tested. For a pointer p, dynamiccast
On the other hand, we may legitimately assume that a reference refers to an object. Consequently, dynamiccast
Navigating Class Hierarchies
When only single inheritance is used, a class and its base classes constitute a tree rooted in a single base class. This is simple but often constraining. When multiple inheritance is used, there is no single root. This in itself doesn’t complicate matters much. However, if a class appears more than once in a hierarchy, we must be a bit careful when we refer to the object or objects that represent that class.
Naturally, we try to keep hierarchies as simple as our application allows (and no simpler). However, once a nontrivial hierarchy has been made we soon need to navigate it to find an appropriate class to use as an interface. This need occurs in two variants. That is,
• Sometimes, we want to explicitly name an object of a base class or a member of a base class.
• At other times, we want to get a pointer to the object representing a base or derived class of an object given a pointer to a complete object or some sub object.
Static and Dynamic Casts
A dynamiccast can cast from a polymorphic virtual base class to a derived class or a sibling class. A staticcast does not examine the object it casts from, so it cannot.
The dynamiccast requires a polymorphic operand because there is no information stored in a non polymorphic object that can be used to find the objects for which it represents a base. For objects of such types, only static type information will be available. However, the information needed to provide runtime type identification includes the information needed to implement the dynamiccast.
Why would anyone want to use a staticcast for class hierarchy navigation? There is a small runtime cost associated with the use of a dynamiccast. More significantly, there are millions of lines of code that were written before dynamiccast became available. This code relies on alternative ways of making sure that a cast is valid, so the checking done by dynamiccast is seen as redundant. However, such code is typically written using the Cstyle cast; often obscure errors remain. Where possible, use the safer dynamiccast. The compiler cannot assume anything about the memory pointed to by a void*. This implies that dynamiccast – which must look into an object to determine its type – cannot cast from a void*. For that, a staticcast is needed.
It is not possible to cast to a private base class, and ‘‘casting away const’’ requires a constcast. Even then, using the result is safe only provided the object wasn’t originally declared const.
Class Object Construction and Destruction
A class object is more than simply a region of memory. A class object is built from ‘‘raw memory’’ by its constructors and it reverts to ‘‘raw memory’’ as its destructors are executed. Construction is bottom up, destruction is top down, and a class object is an object to the extent that it has been constructed or destroyed. This is reflected in the rules for RTTI, exception handling, and virtual functions.
It is extremely unwise to rely on details of the order of construction and destruction, but that order can be observed by calling virtual functions, dynamiccast, or typeid at a point where the object isn’t complete.
At the point of construction, the object isn’t is merely a partially constructed object. It is best to avoid calling virtual functions during construction and destruction.
Typeid and Extended Type Information
The dynamiccast operator serves most needs for information about the type of an object at run time. Importantly, it ensures that code written using it works correctly with classes derived from those explicitly mentioned by the programmer. Thus, dynamiccast preserves flexibility and extensibility in a manner similar to virtual functions.
However, it is occasionally essential to know the exact type of an object. For example, we might like to know the name of the object’s class or its layout. The typeid operator serves this purpose by yielding an object representing the type of its operand.
const typeinfo& typeid(typename) throw(badtypeid) ; / / pseudo declaration
A typeid() is most commonly used to find the type of an object referred to by a reference or a pointer
typeid(r) ; / / type of object referred to by r
typeid(*p) ; / / type of object pointed to by p
typeid(p) ; / / type of pointer, that is, Shape* (uncommon, except as a mistake)
If the value of a pointer or a reference operand is 0, typeid() throws a badtypeid exception.
It is not guaranteed that there is only one typeinfo object for each type in the system. In fact, where dynamically linked libraries are used it can be hard for an implementation to avoid duplicate typeinfo objects. Consequently, we should use == on typeinfo objects to test equality, rather than == on pointers to such objects.
We sometimes want to know the exact type of an object so as to perform some standard service on the whole object (and not just on some base of the object). Ideally, such services are presented as virtual functions so that the exact type needn’t be known.
The character representation of a class’ name is implementation defined. This Cstyle string resides in memory owned by the system, so the programmer should not attempt to delete[] it.
Uses and Misuses of RTTI
One should use explicit runtime type information only when necessary. Static (compiletime) checking is safer, implies less overhead, and – where applicable – leads to better structured programs.
Using dynamiccast rather than typeid would improve this code only marginally.
Many examples of proper use of RTTI arise when some service code is expressed in terms of one class and a user wants to add functionality through derivation.
Even if the user is willing to modify the base classes, such modification may cause its own problems. For example, it may be necessary to introduce dummy implementations of virtual functions in classes for which those functions are not needed or not meaningful.
Pointers to Members
C++ offers a facility for indirectly referring to a member of a class. A pointer to a member is a value that identifies a member of a class. You can think of it as the position of the member in an object of the class, but of course an implementation takes into account the differences between data members, virtual functions, non virtual functions, etc.
Pstdmem s = &Stdinterface::suspend;
(p->*s)() ; // call through pointer to member
A pointer to member can be obtained by applying the address of operator & to a fully qualified class member name, for example, &Stdinterface::suspend. A variable of type ‘‘pointer to member of class X’’ is declared using a declarator of the form X::*. The use of typedef to compensate for the lack of readability of the C declarator syntax is typical. However, please note how the X::* declarator matches the traditional * declarator exactly.
A pointer to member m can be used in combination with an object. The operators ->* and .* allow the programmer to express such combinations. For example, p->* m binds m to the object pointed to by p, and obj.*m binds m to the object obj. The result can be used in accordance with m’s type. It is not possible to store the result of a ->* or a .* operation for later use.
Naturally, if we knew which member we wanted to call we would invoke it directly rather than mess with pointers to members. Just like ordinary pointers to functions, pointers to member functions are used when we need to refer to a function without having to know its name. However, a pointer to member isn’t a pointer to a piece of memory the way a pointer to a variable or a pointer to a function is. It is more like an offset into a structure or an index into an array. When a pointer to member is combined with a pointer to an object of the right type, it yields something that identifies a particular member of a particular object.
Base and Derived Classes
A derived class has at least the members that it inherits from its base classes. Often it has more. This implies that we can safely assign a pointer to a member of a base class to a pointer to a member of a derived class, but not the other way around. This property is often called contravariance.
This contravariance rule appears to be the opposite of the rule that says we can assign a pointer to a derived class to a pointer to its base class. In fact, both rules exist to preserve the fundamental guarantee that a pointer may never point to an object that doesn’t at least have the properties that the pointer promises.
Free Store
It is possible to take over memory management for a class by defining operator new()and operator delete().
Member operator new()s and operator delete()s are implicitly static members. Consequently, they don’t have a this pointer and do not modify an object. They provide storage that a constructor can initialize and a destructor can clean up.
void* operator new(sizet) ;
void operator delete(void*, sizet) ;
if you want to supply an allocator/deallocator pair that works correctly for derived classes, you must either supply a virtual destructor in the base class or refrain from using the sizet argument in the deallocator.
Array Allocation
The operator new() and operator delete() functions allow a user to take over allocation and deallocation of individual objects; operator new[]() and operator delete[]() serve exactly the same role for the allocation and deallocation of arrays.
“Virtual Constructors”
To construct an object, a constructor needs the exact type of the object it is to create. Consequently, a constructor cannot be virtual. Furthermore, a constructor is not quite an ordinary function. In particular, it interacts with memory management routines in ways ordinary member functions don’t. Consequently, you cannot have a pointer to a constructor.
Both of these restrictions can be circumvented by defining a function that calls a constructor and returns a constructed object. This is fortunate because creating a new object without knowing its exact type is often useful.
The type of an overriding function must be the same as the type of the virtual function it overrides, except that the return type may be relaxed. That is, if the original return type was B*, then the return type of the overriding function may be D*, provided B is a public base of D. Similarly, a return type of B& may be relaxed to D&.
Advice
[1] Use ordinary multiple inheritance to express a union of features.
[2] Use multiple inheritance to separate implementation details from an interface.
[3] Use a virtual base to represent something common to some, but not all, classes in a hierarchy.
[4] Avoid explicit type conversion (casts).
[5] Use dynamiccast where class hierarchy navigation is unavoidable.
[6] Prefer dynamiccast overtypeid.
[7] Prefer private to protected.
[8] Don’t declare data members protected.
[9] If a class defines operator delete(), it should have a virtual destructor.
[10] Don’t call virtual functions during construction or destruction.
[11] Use explicit qualification for resolution of member names sparingly and preferably use it in overriding functions
No comments:
Post a Comment