C ++: interface inheritance and implementation inheritance

Source: Internet
Author: User
C ++: interface inheritance and implementation inheritance

(Public) inheritance is a simple and easy-to-understand concept. Once it is closely examined, it will prove that it is composed of two independent parts: inheritance of function interfaces (Inheritance of function interfaces) and Inheritance of function implementations (Inheritance of function implementation ). The differences between the two inheritance exactly match the differences between function declarations (function declaration) and function definitions (Function Definition) discussed in introduction.

As a class designer, sometimes you want Derived classes to inherit only the interface (Declaration) of a member function ). Sometimes you want derivedclasses to inherit both interfaces and implementation, but you need to allow them to replace the implementation they inherit. In other cases, you want Derived classes to inherit the interface and implementation (implementation) of a function, without allowing them to replace anything.

To better understand the differences between these options, consider a classhierarchy (class inheritance system) that represents ry in a graphics application ):

Class shape {
Public:
Virtual void draw () const = 0;

Virtual void error (const STD: string & MSG );

Int objectid () const;

...
};

Class rectangle: Public shape {...};

Class ellipse: Public shape {...};

Shape is an abstract class. Its pure virtual function (pure virtual function) indicates this. As a result, the customer cannot create an instance of shape class, but can only create an instance of classes inherited from it. However, shape has a very strong impact on all classes inherited from it (public), because

The member functions interfaces are always inherited. As explained in item 32, public inheritance means is-a, so anything that is true for a base class must also be true for its derived classes. Therefore, if a function applies to a class, it must also apply to its derived classes.

The shape class declares three functions. First, draw the current object on a clear display device. Second, error. If member functions needs to report an error, call it. Third, objectid, returns the unique integer identifier of the current object. Each function is declared in a different way: draw is a pure virtual function (pure virtual function); error is a simple (impure ?) Virtual function (simple virtual function), while objectid is a non-virtual function (non-virtual function ). What do these different statements imply?

Consider the first pure virtual function (pure virtual function) draw:

Class shape {
Public:
Virtual void draw () const = 0;
...
};

Two of the most notable features of pure virtual functions (pure virtual functions) are that they must be re-declared by any specific class that inherits them, and they are generally not defined in abstract classes. Add these two features together and you should realize that.

The purpose of declaring a pure virtual function is to make Derived classes inherit a function interface only.

This makes the shape: Draw function have a complete meaning, because it requires that all shape objects be properly drawn, however, the shape class itself cannot provide a reasonable default implementation for this function. For example, the algorithm for drawing an ellipse is very different from the algorithm for drawing a rectangle. The shape: Draw statement tells the designer of the derived classes: "You must provide a draw function, but I have no comments on how you implement it."

By the way, it is possible to provide a definition for a pure virtual function. That is to say, you can provide an implementation for shape: draw, and C ++ will not complain about anything, but the only way to call it is to use the class name to limit this call:

Shape * PS = new shape; // error! Shape is abstract

Shape * PS1 = new rectangle; // fine
PS1-> draw (); // CILS rectangle: Draw

Shape * PS2 = new ellipse; // fine
PS2-> draw (); // callellipse: Draw

PS1-> shape: Draw (); // callshape: Draw

PS2-> shape: Draw (); // callshape: Draw

In addition to helping you impress fellow programmers at cocktail parties, this feature is usually useless. However, as you will see below, it can be used as a mechanism to "provide a safer-than-usual implementation for simple (impure) virtual functions.

The story behind simple virtual functions is a little different from that of pure functions. Derived classes still inherits the interface of the function as usual, but simple virtual functions provides an implementation that can be replaced by Derived classes. If you think about it for a while, you will realize

The purpose of declaring a simple virtual function is to let Derived classes inherit a function interface as well ASA default implementation.

Consider the case of shape: Error:

Class shape {
Public:
Virtual void error (const STD: string & MSG );
...
};

The interface requires that each class must support a function called when an error occurs, but each class can use any method that it feels appropriate to handle the error. If a class does not need to do anything special, it can turn to the default version of error handling provided in the shape class. That is to say, the shape: Error statement tells the designer of derived classes: "You should support an error function, but if you do not want to write it yourself, you can ask for the default version in shape class ."

The result is: it is dangerous to allow simple virtual functions to specify both a function interface and a default implementation. Let's take a look at the reason for considering the hierarchy (inheritance system) of an XYZ Airline plane ). XYZ has only two types of aircraft, model A and model B. Both of them fly exactly in the same way. Therefore, XYZ is designed as follows hierarchy (inheritance system ):

Class airport {...}; // represents airports

Class airplane {
Public:
Virtual void fly (const airport & Destination );

...

};

Void airplane: Fly (const airport & Destination)
{
Default Code for flying an airplane to the given destination
}

Class modela: Public airplane {...};

Class modelb: Public airplane {...};

Airplane :: fly is declared as virtual. However, to avoid repeated code in modela and modelb classes, the default flight behavior is provided by the airplane: Fly function body for modela and modelb to inherit.

This is a classic object-oriented design. Because two classes share a common feature (which implements the fly method), this general feature is transferred to a base class and inherited by two classes. This design makes general features clear, avoids code duplication, improves future scalability, and simplifies long-term maintenance-because of the object-oriented technology, all these things are highly sought after. XYZ airlines should be proud of it.

Now, assuming that XYZ's wealth has increased, it is decided to introduce a new model, Model C. Model C is different from model A and model B in some aspects. In particular, its flight is different.

Programmers at XYZ added the class of Model C to hierarchy (inheritance system), but they forgot to redefine the fly function as they rushed to put new models into service:

Class modelc: Public airplane {

... // No Fly function is declared
};

Therefore, in their code, something similar to this occurs:

Airport PDX (...); // PDX is the airport near my home

Airplane * pA = new modelc;

...

Pa-> fly (PDX); // callairplane: fly!

This is a disaster: an attempt to make a modelc object fly like a modela or modelb. This is not an encouraging behavior in the travel crowd.

The problem here is not that airplane: fly has a default behavior, but that modelc is allowed to inherit this line without specifying what it wants to do. Fortunately, it is easy to "provide default behavior for derived classes (derived classes), but they will not be handed over to them unless they make clear requirements. This trick is to cut off the connection between the virtual function interface (Interface) and its default implementation (default implementation. This method is used as follows:

Class airplane {
Public:
Virtual void fly (const airport & Destination) = 0;

...

Protected:
Void defaultfly (const airport & Destination );
};

Void airplane: defaultfly (const airport & Destination)
{
Default Code for flying an airplane to the given destination
}

Note airplane: How is fly converted into a pure virtual function (pure virtual function. It provides an interface for flight ). The default implementation will also appear in airplane class, but now it is an independent function, defafly fly. For example, if modela and modelb need to use the default behavior classes, you only need to make an inline call to defaultfly in their fly function bodies (but refer to the inline and virtual
Interaction information of functions (virtual functions ):

Class modela: Public airplane {
Public:
Virtual void fly (const airport & Destination)
{Destinfly fly (destination );}

...
};

Class modelb: Public airplane {
Public:
Virtual void fly (const airport & Destination)
{Destinfly fly (destination );}

...
};

For modelc class, it is impossible to inherit the incorrect fly implementation accidentally, because the pure virtual (pure virtual) in airplane forces modelc to provide its own fly version.

Class modelc: Public airplane {
Public:
Virtual void fly (const airport & Destination );

...
};

Void modelc: Fly (const airport & Destination)
{
Code for flying a modelc airplane to the given destination
}

This solution is not very secure (programmers can still use copy-and-paste to put themselves in trouble), but it is more reliable than the original design. As for airplane: defaultfly, it is protected because it is completely the Implementation Details of airplane and Its Derived classes (derived class. Customers who use airplanes should only care about how they can fly, rather than how they can fly.

Airplane: defaultfly is a non-virtual function (non-virtual function. This is because derived class (derived class) should not redefine this function, which is a principle introduced in item 36. If defafly fly is virtual, you will encounter a loop problem: what if some derivedclass (derived class) should be redefined but defaultfly is forgotten?

Some people oppose providing functions for interfaces (interfaces) and defaultimplementation (default implementations), just like fly and defaultfly above. First, they noticed that this would cause similar related function names to pollute class namespace. However, they still agree that the interface and default implementation should be separated. How did they solve this apparent conflict? By taking advantage of the fact that pure virtual functions (pure virtual function) must be in concretederived
Classes (Specific Derived classes) are redeclared (re-declared), but they can also have their own implementations. The following describes how airplane hierarchy uses this capability to define a purevirtual function (pure virtual function ):

Class airplane {
Public:
Virtual void fly (const airport & Destination) = 0;

...
};

Void airplane: Fly (const airport & Destination) // an implementation
{// A pure virtual function
Default Code for flying an airplane
The given destination
}

Class modela: Public airplane {
Public:
Virtual void fly (const airport & Destination)
{Airplane: Fly (destination );}

...

};

Class modelb: Public airplane {
Public:
Virtual void fly (const airport & Destination)
{Airplane: Fly (destination );}

...

};

Class modelc: Public airplane {
Public:
Virtual void fly (const airport & Destination );

...

};

Void modelc: Fly (const airport & Destination)
{
Code for flying a modelc airplane to the given destination
}

In addition to replacing the independent function airplane: defaultfly with the pure virtual function (pure virtual function) airplane: Fly function, this design is almost identical to the previous one. Essentially, fly can be split into two basic components. Its declaration (Declaration) specifies its interface (Interface) (which is required by Derived classes (derived class), and its definition (Definition) specify its default behavior (this is usable by the derived classes (derived class), but only when they explicitly require this ). Set fly
Merge with defafly fly. In any case, you lose the ability to give the two functions different protection levels: the original protected code (implemented in defaultfly) now it becomes public (because it is located in Fly ).

Finally, let's look at the shape non-virtualfunction (non-virtual function), objectid:

Class shape {
Public:
Int objectid () const;
...
};

When a member function (member function) is non-virtual (non-virtual), it should not be expected to behave differently in the derived classes (derived class. In fact, a non-virtual member function (non-virtual member function) specifies an invariant over specialization (beyond the special invariant), because no matter how special a derivedclass (derived class) becomes, it regards it as an action that does not allow changes. Except as follows,

The purpose of declaring a non-virtual function is to have derived classes inherit a function interface as well as amandatory implementation (so that the derived class inherits the interface of a function, also inherits a forced implementation ).

You can consider the shape: objectid declaration as follows: "Each shape object generates an object identifier (Object ID code), and this object identifier (Object ID code) this method is always calculated using the same method. This method is determined by the shape: objectid definition, and the derived class (derived class) should not try to change it." Because a non-virtual function (non-virtual function) is considered as an invariant over specialization (beyond the special invariant ),
Class (derived class) should never be redefined. For details, see item 36.

The difference between the declaration of pure virtual, simple virtual, and non-virtual functions allows you to specify exactly what you need to inherit from the derived classes (derived class. Interface only (only interface), interface and a default implementation (interface and a default implementation), and interface and a mandatory implementation (interface and a mandatory implementation ). Because these different declaration types mean different meanings, When you declare your
You must carefully select between member functions. If you do this, you should be able to avoid the two most common errors caused by inexperienced class designers.

The first error is to declare all functions as non-virtual (non-virtual ). This does not leave space for the specialization of derived classes (derived classes); Non-virtualdestructors (non-virtual destructor) is particularly problematic (see item 7 ). Of course, there is a reason to design a class that is not used as a base class (base class. In this case, a set of exclusive non-virtualmember functions (non-virtual member functions) is completely reasonable. However, in more general cases, such classes may be ignorant of the differences between virtual and non-virtual functions (non-virtual functions), or
Virtual functions (virtual functions) have no cost-effective performance concerns. In fact, almost any class used as a base class (base class) will have virtual functions (or refer to item 7 ).

If you are concerned about the cost of virtual functions, please allow me to introduce experience-based 80-20 Rules (see item 30). In a typical program, 80% of the running time is spent on executing 20% of the Code. This rule is very important because it means that, on average, 80% of your function calls can be virtualized without a slight or perceptible impact on the overall performance of your program. Before you step into the shadow of worries about "can you afford a virtual function (virtual function) Cost", you should use some simple preventive measures, to make sure that you are concerned about what makes a decisive difference in your program.
20%.

Another common error is that all member functions (member functions) are virtual ). Sometimes this is correct-the interface classes (Interface Class) of item 31 can be used as evidence. However, it may also be a sign of a class designer who lacks the determination to express his attitude. Some functions should not be redefined in Derived classes (derived class). In this case, you should declare those functions as non-virtual (non-virtual) and clearly express this. It does not serve those people. They assume that if they only need to spend some time redefining all your functions, your class will be used by all people to do everything, if you have
Invariant over Specialization!

Things to remember

· Inheritance of Interface (interface inheritance) is different from inheritance of implementation (Implementation inheritance. In public inheritance (Public inheritance), derived classes (derived class) always inherits base class interfaces (base class interface ).

· Pure virtual functions (pure virtual function) specifies inheritance of interface only (only interfaces are inherited ).

· Simple (impure) virtual functions (simple virtual function) specifies the inheritance of Interface (interface inheritance) plus inheritance of a default implementation (default implementation inheritance ).

· Non-virtual functions (non-virtual function) specifies inheritance of Interface (interface inheritance) plus inheritance of a mandatory implementation (forced inheritance ).

 

 

C ++ interface definition and implementation example

I. Interface Definition

Sometimes, we have to provide some interfaces for others to use. Interfaces are a set of specifications for classes that meet the interface requirements. interfaces can help implement multi-inheritance functions similar to classes, is to provide a way to interact with other systems. To put it bluntly, an interface is a standard defined for objects and is a standard. For example, to define a person object, you must have a meal action. In the defined Westerners object implementation (implements) person interface, you can use a knife and a fork to eat and implement the (implements) person interface in the defined Chinese object, use chopsticks for meals ~! Other systems do not need to know your internal details or internal details. They can only communicate with you through external interfaces. Based on the features of C ++, we can use pure virtual functions. The advantage of doing so is encapsulation and polymorphism. Here is an example for your reference.

Class iperson

{

Public:

Iperson (){};

Virtual ~ Iperson () = 0 {};/* Note: it is best to define this virtual destructor to prevent the subclass from calling the Destructor normally. If it is defined as a pure virtual destructor, it must contain a definition body, because the subclass implicitly calls the destructor. */

// Interfaces provided for external use generally use pure virtual functions

Virtual void setname (const string & strname) = 0;

Virtual const stringgetname () = 0;

Virtual void work () = 0;

}

Ii. Interface implementation

The implementation interface is implemented by inheriting the sub-classes of the interface. Different sub-classes can achieve different effects, even if the so-called polymorphism.

Class cteacher: publiciperson

{

Public:

Cteacher (){};

Virtual ~ Cteacher ();

String m_strname;

Void setname (const string & strname );

Const string getname ();

Void work ();

}

Void cteacher: setname (const string & strname)

{

M_strname = Name;

}

Const string cteacher: getname ()

{

Return m_strname;

}

Void cteacher: Work ()

{

Cout <"I amteaching! "<Endl; // the instructor's job is to teach, and other professionals do different jobs.

}

Iii. Interface Export

Bool getipersonobject (void ** _ rtobject)
{

Iperson * pman = NULL;

Pman = new cteacher ();

* _ Rtobject = (void *) pman;

Return true;

}

_ Declspec (dllexport) boolgetipersonobject (void ** _ rtobject );

Iv. Interface Usage

# Include "iperson. H"

# Pragma comment (Lib, "iperson. lib ")

Bool _ declspec (dllimport) getipersonobject (void ** _ rtobject );

/* Test example */

Void main ()

{

Iperson * _ ipersonobj = NULL;

Void * pobj = NULL;

If (getipersonobject (& pobj ))

{

// Obtain the object

_ Ipersonobj = (iperson *) pobj;

// Call the interface to perform the operation

_ Ipersonobj-> setname ("Tom ");

String strname = _ ipersonobj-> getname;

_ Ipersonobj-> Work ();

}

If (_ ipersonobj! = NULL)

{

Delete _ ipersonobj;

_ Ipersonobj = NULL;

}

}

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.