Analysis of C ++ static and dynamic Polymorphism
Today, C ++ is already a multi-generic programming language (multiparadigm programming lauguage) that supports both procedural and object-oriented (object-oriented) function Form (functional), generic form (generic), metaprogramming language. These capabilities and elasticity make C ++ an unmatched tool, but may also lead to user confusion, such as polymorphism. In these programming generics, object-oriented programming, generic programming, and many new meta-programming forms all support the concept of polymorphism, but they are different. C ++ supports multiple forms of polymorphism. From the perspective of manifestation, there are virtual functions, templates, and overloading. From the perspective of binding time, they can be divided into static and dynamic polymorphism, it is also known as the polymorphism during compilation and runtime.
This article describes the similarities and differences. Note that generic programming and metaprogramming are usually implemented in the form of templates. Therefore, this article mainly introduces two forms: Object-oriented dynamic polymorphism and static polymorphism Based on Template programming. In addition, macros can also be considered as a way to achieve static polymorphism. The implementation principle is to replace the full text, but the C ++ language itself does not like macros. Here, macro polymorphism is ignored ".
What is dynamic polymorphism?
The design concept of dynamic polymorphism: for the related object types, determine a common function set between them, and then in the base class, declare these common functions as multiple common virtual function interfaces. Each subclass overrides these virtual functions to complete specific functions. The client code (Operation Function) is used to reference or pointer to the base class to operate these objects. The call to the virtual function is automatically bound to the actually provided subclass object.
From the above definition, we can also see that dynamic polymorphism is completed at runtime due to virtual functions. It can also be called Runtime polymorphism, this creates the power of the dynamic polymorphism mechanism in processing heterogeneous object sets (of course, there is also a little performance loss ).
Check the Code:
Namespace DynamicPoly
{
Class Geometry
{
Public:
Virtual void Draw () const = 0;
};
Class Line: public Geometry
{
Public:
Virtual void Draw () const {std: cout <Line Draw ();}
};
Class Circle: public Geometry
{
Public:
Virtual void Draw () const {std: cout <Circle Draw ();}
};
Class Rectangle: public Geometry
{
Public:
Virtual void Draw () const {std: cout <Rectangle Draw ();}
};
Void DrawGeometry (const Geometry * geo)
{
Geo-> Draw ();
}
// The most attractive aspect of dynamic polymorphism is the ability to process heterogeneous object sets.
Void DrawGeometry (std: vector VecGeo)
{
Const size_t size = vecGeo. size ();
For (size_t I = 0; I <size; ++ I)
VecGeo [I]-> Draw ();
}
}
Void test_dynamic_polymorphism ()
{
DynamicPoly: Line line;
DynamicPoly: Circle circle;
DynamicPoly: Rectangle rect;
DynamicPoly: DrawGeometry (& circle );
Std: vector Vec;
Vec. push_back (& line );
Vec. push_back (& circle );
Vec. push_back (& rect );
DynamicPoly: DrawGeometry (vec );
}
Dynamic polymorphism is essentially the concept of inheritance and Polymorphism in object-oriented design. The interface in dynamic polymorphism is an explicit interface (virtual function), for example,
Void DoSomething (Widget & w)
{
If (w. size ()> 0 & w! = SomeNastyWidget)
{
Widget temp (w );
Temp. normalize ();
Temp. swap (w );
}
}
For the above Code, this requirement:
Since w is declared as a Widget, w must support the Widget interface, and these interfaces (such as Widget. h) can be found in the source code. Therefore, these interfaces are the Display Interfaces;
A Widget may only be a base class and has a subclass. That is to say, the Widget interface may be a virtual function (such as the normalize above). At this time, the call to the interface will show a running polymorphism;
What is static polymorphism?
Static polymorphism Design Philosophy: for related object types, directly implement their respective definitions without having to have a total of base classes or even have no relationship. Only the same interface declaration is required in the implementation of each specific class. The interface here is called an implicit interface. The client defines the functions that operate on these objects as templates. When you need to operate on objects of any type, you can directly specify the type of real parameters for the template (or get through real-argument deduction ).
Compared with object-oriented programming, explicit interfaces and Runtime polymorphism (virtual functions) are used to achieve dynamic polymorphism. In Template programming and generic programming, static polymorphism is achieved through implicit interface and compiler polymorphism.
Check the Code:
Namespace StaticPoly
{
Class Line
{
Public:
Void Draw () const {std: cout <Line Draw ();}
};
Class Circle
{
Public:
Void Draw (const char * name = NULL) const {std: cout <Circle Draw ();}
};
Class Rectangle
{
Public:
Void Draw (int I = 0) const {std: cout <Rectangle Draw ();}
};
Template
Void DrawGeometry (const Geometry & geo)
{
Geo. Draw ();
}
Template
Void DrawGeometry (std: vector VecGeo)
{
Const size_t size = vecGeo. size ();
For (size_t I = 0; I <size; ++ I)
VecGeo [I]. Draw ();
}
}
Void test_static_polymorphism ()
{
StaticPoly: Line line;
StaticPoly: Circle circle;
StaticPoly: Rectangle rect;
StaticPoly: DrawGeometry (circle );
Std: vector VecLines;
StaticPoly: Line line2;
StaticPoly: Line line3;
VecLines. push_back (line );
VecLines. push_back (line2 );
VecLines. push_back (line3 );
// VecLines. push_back (& circle); // compilation error, no longer able to process heterogeneous objects
// VecLines. push_back (& rect); // compilation error, no longer able to process heterogeneous objects
StaticPoly: DrawGeometry (vecLines );
Std: vector VecCircles;
VecCircles. push_back (circle );
StaticPoly: DrawGeometry (circle );
}
Static polymorphism is essentially the presence of templates. An interface call in a static polymorphism is also called an implicit interface. Compared with a display interface, the interface is composed of the function signature (that is, the function name, parameter type, and return type). An implicit interface is usually composed of valid expressions, for example,
Template
Void DoSomething (Widget & w, const Other & someNasty)
{
If (w. size ()> 0 & w! = SomeNasty) // someNastyT may be a T-type instance or
{
Widget temp (w );
Temp. normalize ();
Temp. swap (w );
}
}
This seemingly requires:
The type T must support the size, normalize, swap, and copy constructor functions, and can be compared unevenly.
Type T indicates that different functions are called only when the template is made available during the compilation period. At this time, the interface call shows polymorphism during the compilation period.
However,
The size function does not need to return an integer value to compare with 10, or even return a value type. The only constraint is that it returns an object of the X type, in addition, an operator> can be called for the X object and int type (value 10). This operator> is not necessarily required for an X parameter, it can convert the X type to the Y type object through implicit conversion, but only the Y type can be compared with the int type (good detour, please refer, this also proves that it is difficult to solve template programming compilation errors ).
Operator is not required for the same type of T! =, While only T can be converted to X type objects, someNastyT can be converted to Y type objects, and X and Y can be compared.
Comparison of dynamic and static Polymorphism
Static Polymorphism
Advantages:
Static polymorphism is completed during the compilation period, so the efficiency is high and the compiler can also be optimized;
Strong adaptability and loose coupling. For example, special types can be processed through special and full special features;
The most important thing is that static polymorphism brings the concept of generic design to C ++ through template programming, such as a powerful STL library.
Disadvantages:
Because it is a template to achieve static polymorphism, the shortcomings of the template are also the disadvantages of static polymorphism, such as debugging difficulties, compilation time consumption, code expansion, and compatibility supported by the compiler.
Unable to process heterogeneous object sets
Dynamic Polymorphism
Advantages:
OO design, intuitive understanding of the objective world;
Implementation and interface separation, reusable
Powerful power of processing heterogeneous object sets under the same inheritance system
Disadvantages:
Binding during runtime, resulting in a certain amount of runtime overhead;
The compiler cannot optimize virtual functions.
The heavy class inheritance system affects the entire class hierarchy;
Differences:
In essence, static polymorphism is determined during the compilation period and is implemented by the template. Dynamic polymorphism is determined at runtime and implemented by inheritance and virtual functions;
In dynamic polymorphism, the interface is explicit, with the function signature as the center, and the polymorphism is implemented through the virtual function at runtime. The static multi-platform interface is implicit, and the valid expression is the center, polymorphism is completed through the template with the current compilation period
Similarities:
All of them can achieve polymorphism, static polymorphism/compilation polymorphism, dynamic polymorphism/Runtime polymorphism;
Both interfaces and implementations can be separated. One is the template-defined interface, and the other is the implementation of the type parameter definition. The other is the basic class virtual function definition interface, and the inheritance class is responsible for implementation;
Attach all the code for this test:
Namespace DynamicPoly
{
Class Geometry
{
Public:
Virtual void Draw () const = 0;
};
Class Line: public Geometry
{
Public:
Virtual void Draw () const {std: cout <Line Draw ();}
};
Class Circle: public Geometry
{
Public:
Virtual void Draw () const {std: cout <Circle Draw ();}
};
Class Rectangle: public Geometry
{
Public:
Virtual void Draw () const {std: cout <Rectangle Draw ();}
};
Void DrawGeometry (const Geometry * geo)
{
Geo-> Draw ();
}
// The most attractive aspect of dynamic polymorphism is the ability to process heterogeneous object sets.
Void DrawGeometry (std: vector VecGeo)
{
Const size_t size = vecGeo. size ();
For (size_t I = 0; I <size; ++ I)
VecGeo [I]-> Draw ();
}
}
Namespace StaticPoly
{
Class Line
{
Public:
Void Draw () const {std: cout <Line Draw ();}
};
Class Circle
{
Public:
Void Draw (const char * name = NULL) const {std: cout <Circle Draw ();}
};
Class Rectangle
{
Public:
Void Draw (int I = 0) const {std: cout <Rectangle Draw ();}
};
Template
Void DrawGeometry (const Geometry & geo)
{
Geo. Draw ();
}
Template
Void DrawGeometry (std: vector VecGeo)
{
Const size_t size = vecGeo. size ();
For (size_t I = 0; I <size; ++ I)
VecGeo [I]. Draw ();
}
}
Void test_dynamic_polymorphism ()
{
DynamicPoly: Line line;
DynamicPoly: Circle circle;
DynamicPoly: Rectangle rect;
DynamicPoly: DrawGeometry (& circle );
Std: vector Vec;
Vec. push_back (& line );
Vec. push_back (& circle );
Vec. push_back (& rect );
DynamicPoly: DrawGeometry (vec );
}
Void test_static_polymorphism ()
{
StaticPoly: Line line;
StaticPoly: Circle circle;
StaticPoly: Rectangle rect;
StaticPoly: DrawGeometry (circle );
Std: vector VecLines;
StaticPoly: Line line2;
StaticPoly: Line line3;
VecLines. push_back (line );
VecLines. push_back (line2 );
VecLines. push_back (line3 );
// VecLines. push_back (& circle); // compilation error, no longer able to process heterogeneous objects
// VecLines. push_back (& rect); // compilation error, no longer able to process heterogeneous objects
StaticPoly: DrawGeometry (vecLines );
Std: vector VecCircles;
VecCircles. push_back (circle );
StaticPoly: DrawGeometry (circle );
}
/** Compilation failed. Therefore, the Widget requires an explicit interface, but it cannot be seen now */
// Void DoSomething (Widget & w)
//{
// If (w. size ()> 0 & w! = SomeNastyWidget)
//{
// Widget temp (w );
// Temp. normalize ();
// Temp. swap (w );
//}
//}
/** It can be compiled. Therefore, it only requires that the template be compiled only when the template is ready. (no call or code is available )*/
Template
Void DoSomething (Widget & w, const Other & someNasty)
{
If (w. size ()> 0 & w! = SomeNasty) // someNastyT may be a T-type instance or
{
Widget temp (w );
Temp. normalize ();
Temp. swap (w );
}
}