Item 37: Never redefine the inherited default parameter value of a function (the default parameter value obtained through inheritance)
By Scott Meyers
Translator: fatalerror99 (itepub's nirvana)
Release: http://blog.csdn.net/fatalerror99/
We started to simplify this topic. Only two functions can be inherited by you: Virtual and non-virtual ). However, redefining an inherited non-virtual function (a non-virtual function derived from inheritance) is always an error (see item 36 ), so we can safely limit our discussion to the fact that you inheritVirtualFunction with a default parameter value (a virtual function with a default parameter value.
In this case, the reason for this item becomes very straightforward: virtual functions (virtual function) is dynamically bound (dynamic binding), and default parameter values (default parameter value) is statically bound (static binding ).
What then? You said that the difference between static and dynamic binding has long been overwhelmed by your mind? (Do not forget, static binding also usesEarly binding(Pre-binding), while dynamic binding (dynamic binding) alsoLate binding(Later binding .) So let's review it.
An objectStatic type(Static type) is the type that you declare to it in the program text ). Consider this class hierarchy (class inheritance system ):
// A class for geometric shapes
Class shape {
Public:
Enum shapecolor {red, green, blue };
// All shapes must offer a function to draw themselves
Virtual void draw (shapecolor color = Red) const = 0;
...
};
Class rectangle: Public shape {
Public:
// Notice the different default parameter value-bad!
Virtual void draw (shapecolor color = green) const;
...
};
Class circle: Public shape {
Public:
Virtual void draw (shapecolor color) const;
...
};
Intuitively, it looks like this:
Now consider these pointers (pointers ):
Shape * pS; // static type = shape *
Shape * Pc = new circle; // static type = shape *
Shape * Pr = new rectangle; // static type = shape *
In this example, PS, PC, and PR are all declared as pointer-to-shape, so they are all regarded as their static type (static type ). Note that there is no difference in what they actually point to-in any case, their static type (static type) is shape *.
An objectDynamic type(Dynamic type) depends on the type of the currently referenced object (object ). That is to say, its dynamic type (dynamic type) indicates its behavior. In the above example, the dynamic type (dynamic type) of the PC is circle *, while the dynamic type (dynamic type) of the PR is rectangle *. As for PS, it does not have an actual dynamic type (dynamic type), because it (also) cannot reference any object (object ).
Dynamic types (dynamic type), as its name implies, can be changed in the program running, especially through assignments (Value assignment ):
PS = pc; // PS dynamic type is
// Now circle *
PS = Pr; // PS's dynamic type is
// Now rectangle *
Virtual functions (virtual function) isDynamically bound(Dynamic binding) means that the specific function to be called depends on the dynamic type (dynamic type) of the object (object) used to call it ):
PC-> draw (shape: Red); // callcircle: Draw (shape: Red)
Pr-> draw (shape: Red); // callrectangle: Draw (shape: Red)
I know, this is all a story; you have understood virtual functions ). However, when you consider virtual functions with default parameter values (a virtual function with the default parameter value), it is totally messy because, as mentioned above, virtual functions (virtual function) it is dynamically bound (dynamic binding), but the default parameters (default parameter) is statically bound (static binding ). This means that you finally call a definition inDerived classThe virtual function in (derived class) usesBase Class(Base class) default parameter value (default parameter value ).
Pr-> draw (); // callrectangle: Draw (shape: Red )!
In this case, the dynamic type (dynamic type) of PR is rectangle *, so as you want, the virtual function of rectangle is called. In rectangle: draw, the default parameter value (default parameter value) is green. However, because PR's static type (static type) is shape *, the default parameter value (default parameter value) called by this function is obtained from the shape class, rather than the rectangle class! The result is that a call is made up of a mix of the draw declarations in the strange and almost unexpected shapes and rectangle classes (classes.
The fact that PS, PC, and PR are pointers (pointers) has no causal relationship with this problem. If they are references (references), the problem will still exist. The only important thing is that draw is a virtual function, and its default parameter values (default parameter value) is redefined in a derived class (derived class.
Why does C ++ stick to this abnormal action? The answer is to improve the running efficiency. If the default parameter values (default parameter value) is dynamically bound (dynamic binding), compilers (compiler) must provide a way to determine virtual functions (virtual functions) at runtime) the default value (s) of parameters (parameters), which is slower and more complex than the current mechanism for determining them during the compilation period. The final decision is biased towards the speed and simplicity of implementation. The result is that you can now enjoy efficient operation. However, if you forget to pay attention to the suggestions of this item, it will be confused.
This is complete and perfect, but let's see if you try to follow this rule as base (base class) and derived classes (derived class) when the user provides the same default parameter values (default parameter value), what will happen:
Class shape {
Public:
Enum shapecolor {red, green, blue };
Virtual void draw (shapecolor color= Red) Const = 0;
...
};
Class rectangle: Public shape {
Public:
Virtual void draw (shapecolor color= Red) Const;
...
};
Oh, code duplication (repeated code ). Code duplication (Code duplication) brings dependencies (dependency): If the default parameter values (default parameter value) in the shape changes, all the derived classes (derived class) that repeat it) must change at the same time. Otherwise, they will get stuck in redefining an inherited default parameter value (default parameter value obtained through inheritance ). What should we do?
When you want a virtual function to run in the desired way, it is wise to consider an alternative design, in addition, item 35 provides an alternative to multiple virtual functions. One of the alternatives isNon-virtual interface idiom(Nvi idiom) (non-Virtual Interface Usage): uses the Public Non-virtual function (Public Non-virtual function) in the base class (base class) to call Derived classes (derived class) A private virtual function that may be redefined ). Here, we use non-virtual function (non-virtual function) to specify the default parameter (default parameter), while using virtual function (virtual function) to do the actual work:
Class shape {
Public:
Enum shapecolor {red, green, blue };
Void draw (shapecolor color = Red) const // now non-virtual
{
Dodraw (color); // calla virtual
}
...
PRIVATE:
Virtual void dodraw (shapecolor color) const = 0; // The actual work is
}; // Done in this func
Class rectangle: Public shape {
Public:
...
PRIVATE:
Virtual voidDodraw(Shapecolor color) const; // note lack of
... // Default Param Val.
};
Because non-virtual functions (non-virtual functions) should never be overwritten by Derived classes (derived classes) overridden (refer to item 36), this design makes the color parameter (parameter) of draw) the default value of should always be red.
Things to remember
- Never redefine an inherited default parameter value (default parameter value obtained through inheritance), because the default parameter value (default parameter value) is statically bound (static binding ), virtual functions -- should be the only function you can overwrite (overwrite) -- dynamically bound (dynamic binding ).