Turn: C ++ polymorphism

xiaoxiao2021-03-05  28

C polymorphism of technology: glory

Submit: EASTVC Release Date: 2003-12-14 19:38:12

Original source:

http://www.royaloo.com/articles/articles_2003/polymorphismincpp_content.htm

Summary

This article describes the various polymorphisms in C . The focus is described with object-oriented dynamic polymorphism and template-based static polymorphism, and initially explores the combination of two technologies.

Key words

Multi-state inheritance virtual function template macro function overload generic programming generic mode

preface

Polymorphism, originally originated from Greek Polumorphos, meaning in a variety of forms or forms. In the field of programming, a widely recognized definition is "A ability to associate different special behaviors and single generalization marks". Unlike pure object-oriented programming languages, the polymorphism in C has a broader meaning. In addition to the common dynamic polymorphism of the running period, the template is also allowed to associate different special behaviors and single generalization marks in the case of the compile period in addition to the dynamic polymorphism of the runtime. Operation period, thus is called static polymorphism.

In fact, the macro and function overload mechanism with variables also allows different special behaviors and single generalized marks. However, habits we don't show the behavior they show it as polymorphism (or static). Today, when we talk about polymorphism, if there is no clear, the default is dynamic polymorphism, and static polymorphism means a template-based polymorphism. However, in this article in the theme of C various polymorphics technology, we first review another "polymorphism" for the C community: Function Polymorphism, and "Macro Polymorphism".

Function polymorphism

That is, we often say function overloading. Based on different parameter lists, the same function name can point to different function definitions:

// Overload_Poly.cpp

#include

#include

// Define two overload functions

INT MY_ADD (int A, INT B)

{

RETURN A B;

}

INT MY_ADD (int A, std :: string b)

{

Return A ATOI (B.C_STR ());

}

int main ()

{

INT i = MY_ADD (1, 2);

// Two integers plus

INT S = MY_ADD (1, "2");

// A integer and a string plus

Std :: cout << "i =" << i << "/ n";

Std :: cout << "s =" << s << "/ n";

}

According to the different parameter list (type, number or both), MY_ADD (1, 2) and my_add (1, "2") are compiled into MY_ADD (int, int) and my_add (int, std: : String) Call. The implementation principle is that the compiler reforms the same name function according to a different parameter list, and the same name function becomes a function different from each other. For example, maybe a compiler will reform to MY_ADD_INT_INT () and my_add_int_inter () and MY_ADD_INT_STR (). Macro

Macros with variables can realize a static polymorphism of primary form:

// macro_poly.cpp

#include

#include

// Define the generalization mark: Macro Add

#define add (a, b) (a) (b);

int main ()

{

INT I1 (1), I2 (2);

Std :: string S1 ("Hello,"), S2 ("World!");

INT i = add (i1, i2);

// Two integers plus

Std :: string s = add (S1, s2);

// Two strings "add"

Std :: cout << "i =" << i << "/ n";

Std :: cout << "s =" << s << "/ n";

}

When the program is compiled, the expression add (i1, i2), and the add (S1, S2) are replaced with two integers to add and add specific expressions plus two strings. The integer is added to reflect, and the string is added to the connection. The result of the output of the program is in line with the intuition:

1 2 = 3

Hello, World! = Hello, World!

Dynamic polymorphism

This is a well-known polymorphism. Modern object-oriented language is consistent with this concept. Its technical foundation is inheriting mechanisms and virtual functions. For example, we can define an abstract base class VEHICLE and two derived specific classes of Vehicle CAR and AirPlane:

// Dynamic_Poly.h

#include

// Public abstract base class VEHICLE

Class Vehicle

{

PUBLIC:

Virtual void run () const = 0;

}

// Delicate the specific class car in Vehicle

Class Car: Public Vehicle

{

PUBLIC:

Virtual void Run () const

{

Std :: Cout << "Run a car / n";

}

}

// Delicate the specific class of Vehicle AirPlane

Class AirPlane: Public Vehicle

{

PUBLIC:

Virtual void Run () const

{

Std :: Cout << "Run A AirPlane / N";

}

}

The client can manipulate the specific object by pointing to the pointer (or reference) of the base class VEHICLE. Call a virtual function by pointing to a pointer (or reference) to the base class object, resulting in the call of the corresponding member of the specific object being directed:

// Dynamic_Poly_1.cpp

#include

#include

#include "dynamic_poly.h"

/ / Any Vehicle by a pointer RUN

Void Run_Vehi (const vehicle * vehicle) {

Vehicle-> run ();

/ / Call the corresponding Run () according to the specific type of VEHICLE

}

int main ()

{

Car Car;

AirPlane Airplane;

Run_VEHICLE (& CAR);

// Call Car :: Run ()

Run_VEHICLE (& AirPlane);

// Call AirPlane :: Run ()

}

In this case, the key polymorphic interface element is a virtual function run (). Since the parameters of Run_Vehi () are pointers pointing to the base class VEHICLE, it is impossible to determine which version of Run () is used in the compile period. In the runtime, the full dynamic type of the object that the virtual function called by the function is called. In this way, the Run_VEHICLE () is called for a Car :: run (), and AirPlane :: run () will be called for AirPlane objects.

Perhaps dynamic polymorphism is the ability to deal with heterogeneous objects:

// Dynamic_Poly_2.cpp

#include

#include

#include "dynamic_poly.h"

// Run heterogene collection

Void Run_VEHICLES (Const std :: Vector & vehicles)

{

For (unsigned int i = 0; i

{

Vehicles [I] -> Run ();

/ / Call the corresponding RUN () according to the type of Vehicle

}

}

int main ()

{

Car Car;

AirPlane Airplane;

Std :: vector v;

// Hetero-Vehicles Collection

v.push_back (& ​​car);

v.push_back (& ​​airplane);

Run_VEHICLES (V);

// Run different types of Vehicles

}

In Run_VEHICLES (), VEHICLES [I] -> Run () calls different member functions based on the type being iterative. This refers to an object-oriented programming style from one side.

Static polymorphism

If the dynamic polymorphism is to express the common interface through the virtual function, then the static polymorphism is to express the commonality through "specific classes that are individually defined but supported by the common operation", in other words, there must be the necessary works member functions .

We can rewrite examples of the previous section with a static polymorphism mechanism. This time, we no longer define the Vehicles class hierarchy. In contrast, we write specific class car and airplane who have nothing to do with each other (they all have a run () member function):

// static_poly.h

#include

// Specific class Car

Class Car

{

PUBLIC:

Void Run () const

{

Std :: Cout << "Run a car / n";

}

}

// Specific class AirPlane

Class AirPlane

{

PUBLIC:

Void Run () const

{

Std :: Cout << "Run A AirPlane / N";

}

}

Run_VEHICLE () application is rewritten as follows:

// static_poly_1.cpp

#include

#include

#include "static_poly.h" // Any Vehicle by reference

Template

Void Run_Vehi (Const Vehicle & Vehicle)

{

Vehicle.run ();

/ / Call the corresponding Run () according to the specific type of VEHICLE

}

int main ()

{

Car Car;

AirPlane Airplane;

Run_VEHICLE (CAR);

// Call Car :: Run ()

Run_VEHICLE (AirPlane);

// Call AirPlane :: Run ()

}

Now Vehicle is used as a template parameter rather than a public base class object (in fact, the Vehicle here is just a symbol, there is no intention. After the compiler is handled, we will eventually get two different functions of Run_VEHICLE () and run_vehicle (). This is different from the dynamic polymorphism, and the dynamic polymorphism is only one Run_VEHICLE () function in the runtime.

We cannot deal with the heterogeneous object collection because all types must be determined in the compile period. However, introducing different collections for different Vehicles is just a hand. Since there is no need to limit the collection element to a pointer or reference, we can now benefit from both execution performance and type security:

// static_poly_2.cpp

#include

#include

#include "static_poly.h"

// Run homogeneous Vehicles collection

Template

Void Run_VEHICLES (Const std :: vector & vehicles)

{

For (unsigned int i = 0; i

{

Vehicles [i] .run ();

/ / Call the corresponding Run () according to the specific type of Vehicle

}

}

int main ()

{

Car Car1, Car2;

AirPlane Airplane1, AirPlane2;

Std :: vector vc;

// Homogeneous CARS collection

vc.push_back (car1);

vc.push_back (car2);

//vc.push_back (AirPlane1); // error: Type does not match

Run_VEHICLES (VC);

// Run Cars

Std :: vector vs;

// Homoa Airplanes collection

vs.push_back (airplane1);

vs.push_back (airplane2);

//vs.push_back(Car1); // Error: Type does not match

Run_VEHICLES (VS);

// Run AirPlanes

}

Binding of two polymorphisms

In some advanced C applications, we may need to use dynamic polymorphism and static polymorphisms to achieve elegance, safety, and efficient to achieve object operations. For example, we hope to handle the RUN issues of Vehicles, and hopes "safe and efficient" to complete the difficult movement such as "air fuel" in the air. To this end, we first rewrite the above VEHICLES class hierarchy:

// dscombine_poly.h

#include #include

// Public abstract base class VEHICLE

Class Vehicle

{

PUBLIC:

Virtual void run () const = 0;

}

// Delicate the specific class car in Vehicle

Class Car: Public Vehicle

{

PUBLIC:

Virtual void Run () const

{

Std :: Cout << "Run a car / n";

}

}

// Delicate the specific class of Vehicle AirPlane

Class AirPlane: Public Vehicle

{

PUBLIC:

Virtual void Run () const

{

Std :: Cout << "Run A AirPlane / N";

}

Void Add_oil () Const

{

Std :: cout << "Add oil to airplane / n";

}

}

// Delicate the specific class of Vehicle AirShip

Class AirShip: Public Vehicle

{

PUBLIC:

Virtual void Run () const

{

Std :: Cout << "Run A Airship / N";

}

Void Add_oil () Const

{

Std :: Cout << "Add oil to airship / n";

}

}

Our ideal application can be written as follows:

// dscombine_poly.cpp

#include

#include

#include "dscombine_poly.h"

// Run heterogene collection

Void Run_VEHICLES (Const std :: Vector & vehicles)

{

For (unsigned int i = 0; i

{

Vehicles [I] -> Run ();

// Call the corresponding Run () according to the specific Vehicle type

}

}

/ / For a particular specified Aircraft homogeneous object collection "air refueling"

Template

Void add_oil_to_AIRCRAFTS_IN_THE_SKY (const std :: vector & aircrafts)

{

For (unsigned int I = 0; i

{

Aircrafts [i] .add_oil ();

}

}

int main ()

{

Car Car1, Car2;

AirPlane Airplane1, AirPlane2;

Airship Airship1, Airship2;

Std :: vector v;

// Hetero-Vehicles Collection

v.push_back (& ​​car1);

v.push_back (& ​​airplane1);

v.push_back (& ​​airship1);

Run_VEHICLES (V);

// Run different kinds of Vehicles

Std :: Vector VP;

// Homoa Airplanes collection

vp.push_back (airplane1);

vp.push_back (airplane2);

Add_oil_to_AIRCRAFTS_IN_THE_SKY (VP); // For AirPlanes, "Air Cleaning"

Std :: vector vs;

// Homoa Airships Collection

vs.push_back (airship1);

vs.push_back (airship2);

Add_oil_to_AIRCRAFTS_IN_THE_SKY (VS);

/ / For Airship, "Air Clea"

}

We have retained the class hierarchical structure to be able to use Run_VEHICLES () to effectively deal with the RUN problem of the heterogeneous object set VEHICLES. At the same time, use the function template add_oil_to_aircrafts_in_the_sky (), we can still handle specific types of Vehicles - Aircrafts (including AirPlanes and AirShips) issues. Among them, we avoid the use of pointers to achieve the expected goals in both performance and type safety.

Conclusion

For a long time, the C community has always argued that the connotation and extension of the polymorphism. On the network forum like Comp.Object, this topic is still visible now. Some people have called dynamic polymorphism as include Polymorphism, and static polymorphism is called Parametric Polymorphism or Parameterized Polymorphism.

I noticed a copy of Stamford University in 2003.

C And Object-Oriented Programming Chemicals Clearly refer to Function Multimetage: Function Overloading is Also Referred To As Function Polymorphism As It Involves One Function Having Many Forms. The "Reference" unit after the article gives this web link.

Maybe you first see the term macro polymorphism. Don't be surprised - maybe I am the "first person" of this term. Obviously, the replacement mechanism of macro (or a macro or pseudo-function macro similar to the function) except for the call overhead of the small function, it also shows a similar polymorphism. In our example, the string is added to the inactive connection operation, which is in fact being supported by the bottom operator overloading mechanism (operator overloading). It is worth noted that some people in the C community are called the polymorphism of operator overloading as Ad Hoc Polymorphism.

David Vandevoorde and Nicolai M. JOSUTTIS in their work

C Templates: The static polymorphism and dynamic polymorphism techniques are systematically elaborated in the book. Because it is considered to be "not related to other language mechanisms", this book does not mention "Hong Diversification" (and "Function Polymorphism"). (It should be noted that the author is one of the traditional Chinese translators of this book, this article is based on this book, Chapter 14

The Polymorphic Power of Templates is written)

Dynamic polymorphism only requires a polymorphic function, and the generated executable code size is small, and the static polymorphism must produce different template entities for different types. The size will be large, but the generated code will be faster because there is no need to pass pointers Conduct indirect operation. Static polymorphism is more secure than dynamic polymorphism, because all bindings are checked in the compile period. As shown in the previous example, you cannot insert an object of an error into the container instantiated from a template. In addition, as you have seen, the dynamic polymorphism can effectively deal with the collection of heterogeneous objects, while static polymorphism can be used to achieve safety, efficient homogeneous object collection operations. Static polymorphism is C brings the concept of generic programming. Wide programming can be considered a template programming that "component function is based on the framework". STL is a model of generic programming. STL is a framework that provides a large number of algorithms, containers and iterators, all of which are implemented in template technology. In theory, STL's functionality can of course use dynamic polymorphism, but this performance must be a discount.

Static polymorphism also brings the concept of generic pattern to the C community. In theory, each design pattern that needs to be supported by virtual functions and classes can be achieved with template-based static polymorphism (even combined with dynamic polymorphism and static polymorphism). As you can see, Andrei Alexandrescu's genius works

Modern C Design: Generic Programming and Design Patterns Applied (Addison-Wesley) and Loki Base Library have already been in front of us.

references

1. David Vandevoorde, Nicolai M. Josuttis,

C Templates: The Complete Guide, Addison Wesley, 2002.

2. Chris Neumann, CS193D (SUMMER 2003)

C and Object-Oriented Programming,

Http://www.stanford.edu/class/cs193d/, 2003.

转载请注明原文地址:https://www.9cbs.com/read-35272.html

New Post(0)