Reading Notes Objective c ++ Item 41 understand implicit interfaces and polymorphism during compilation.
1. display the interface and Runtime polymorphism
The world of object-oriented programming revolves around explicit interfaces and Runtime polymorphism. For example, consider the following class (meaningless class ),
1 class Widget { 2 public: 3 Widget(); 4 virtual ~Widget(); 5 6 virtual std::size_t size() const; 7 virtual void normalize(); 8 9 void swap(Widget& other); // see Item 2510 11 ... 12 13 };
Consider the following functions (also meaningless ),
1 void doProcessing(Widget& w) 2 3 { 4 5 if (w.size() > 10 && w != someNastyWidget) { 6 7 Widget temp(w); 8 9 temp.normalize();10 11 temp.swap(w);12 13 }14 15 }
For w in doProcessing, we can say this:
- Because w is declared as the Widget type, w must support the Widget interface. We can search for this interface in the source code (for example, in the header file of the Widget) to know exactly what it looks like, so I call it an explicit interface-an interface that can be explicitly seen in the source code.
- Because some member functions in the Widget are virtual, w's call to these functions will show the Runtime polymorphism: w determines which function is called based on the dynamic type of w at runtime.
2. Implicit interface and polymorphism during compilation
The world of templates and generic programming has fundamentally changed. In this world, explicit interfaces and Runtime polymorphism continue to exist, but they are no longer as important as before. On the contrary,Implicit interface and polymorphism during compilationMoved to the front-end. To understand what it looks like, we can convert doProcessing from a function to a function template to see what will happen:
1 template<typename T> 2 3 void doProcessing(T& w) 4 5 { 6 7 if (w.size() > 10 && w != someNastyWidget) { 8 9 T temp(w);10 11 temp.normalize();12 13 temp.swap(w);14 15 }16 17 }
What can we say to w in doProcessing?
- The interfaces that W must support are determined by the Operations w needs to perform in the template. For example, type T of w must support size, normalize and swap member functions, copy constructor (to create temp), and compare them with someNastyWidget ). We will soon find that this is not very accurate, but it is enough for now. It is important that these expressions must be supported by T.Implicit InterfaceThey must be valid for the template so that the template can be compiled.
- For operator> and operator involving w! = Such a function call may involve template instantiation to make the call successful. These instantiation occurs during compilation. Because the function templates instantiated with different template parameters will cause different functions to be called, this is called"Polymorphism during compilation".
3. Differences between the display interface and the implicit Interface
3.1 display interface features
Even if you never use a template, you should be familiar with the differences between Runtime polymorphism and compilation polymorphism, this is similar to deciding which overload function to call and which virtual function to bind during runtime. The difference between implicit and explicit interfaces is a new concept for templates. However, an explicit interface consists of function signatures, that is, function names, parameter types, return value types, and so on. The public interface of the Widget class, for example:
1 class Widget {2 public:3 Widget();4 virtual ~Widget();5 virtual std::size_t size() const;6 virtual void normalize();7 void swap(Widget& other);8 };
It consists of a constructor, A destructor, and function size, normalize, swap, parameter type, return value type, and constants of these functions. (It also contains the copy constructor generated by the compiler and the copy assignment operator-see Item 5 ). It can also contain typedef and data members, if you are bold enough to violate the Item22 suggestions (declare the data member as private ). This is not the case in this example.
3.2 features of implicit Interfaces
An implicit interface is very different. It is not based on the function signature. It is composed of valid expressions. Let's take a look at the conditional expression at the beginning of the doProcessing template:
1 template<typename T>2 void doProcessing(T& w)3 {4 if (w.size() > 10 && w != someNastyWidget) {5 ...
The implicit interface of T (w type) seems to have the following restrictions:
- It must provide a member function named size and return an integer value.
- It must be supported! = Operator function, which can compare two T-type objects. (Here, we assume that the someNastWidget type is T .)
ThanksOperator overloadAnd neither of the preceding restrictions must be met. T must support a size member function. It is worth mentioning that this function may inherit from a base class. However, this member function does not need to return an integer value. You do not need to return a numeric value. In this case, it does not even need to return the value required in the operator> definition. What he needs is to return an object of Type X, so he can call operator> on an object of Type X and int (because 10 is of the int type. But Operator> there is no need to include a parameter of Type X, because it can also contain a parameter of Type Y, as long as Y can be implicitly converted to X.
Similarly, T does not need to support operator! =, Because operator! = Parameters with a type of X and a type of Y are acceptable. As long as T can be converted to X and the someNastyWidget type can be converted to Y, function call is effective.
(In other words, this analysis does not consider the possibility of overloading operator &. In this way, the meaning of the above expression is converted from a connector into something of other significance .)
Most people have a headache when thinking about this implicit conversion for the first time. You don't need to take aspirin. The implicit interface is simply composed of some valid expressions. The expression itself looks complicated, but the restrictions above are generally simple and straightforward. For example, consider the following conditional expression,
1 if (w.size() > 10 && w != someNastyWidget) ...
It is hard to say that the function size, operator>, operator & or operator! = What are the restrictions, but it is easy to identify the restrictions on the entire expression. The condition part of the If Declaration must be a boolean expression, so no matter what type is involved, no matter w. size ()> 10 & w! = What does someNastyWidget generate? It must be compatible with bool. This is part of the implicit interface that the template doProcessing imposes on type parameter T. The interfaces required by the remaining doProcessing are the call to the copy constructor, and swap must be valid for type T.
The implicit interface imposed on template parameters is the same as the display interface forced on class objects. Both are checked during compilation. You cannot use a Class Object (not compiled) in a conflicting manner with the display interface provided by the same class, or try to use an object in a template, unless this object supports implicit conversions required by the template (otherwise it cannot be compiled)
4. Summary
- Both classes and templates support interfaces and polymorphism.
- For the class, the interface is displayed, centered on the function signature. Polymorphism occurs at runtime and is implemented through virtual functions.
- For template parameters, the interface is implicit based on valid expressions. Template polymorphism is implemented through template instantiation and function overloading. It occurs during compilation.