- Article 36 Never redefine inherited non-virtual functions
- Article 37 Never redefine inherited default parameter values
Article 36: Never redefine inherited non-virtual functions
Use an example to expand the content of the article. Assume that Class D is a derived class B, and that there is a public member function MF in class B:
class B{public: void mf(); ……};class D: public B {……};
Called by the Way
D x;B* pB=&x;pB->mf();D* pD=&x;pD->mf();
Does the above two call function mf
get the same behavior? It mf
's a non-virtual function, but if you have a version of your definition in Class D mf
, the behavior is really different.
class D: public B {public: void mf();//遮掩了B::mf。见条款33……};pB->mf();//调用B::mfpD->mf();//调用D::mf)。
The behavior is inconsistent because the non-virtual function is statically bound (statically bound, clause 37). PB is declared as a pointer-to-b, and the non-virtual function called by PB is always the version defined by B. But the virtual function is dynamic bound (dynamically bound, clause 37), so the virtual function is not constrained by the fact that the function called by a pointer is actually the function that the pointer actually points to the object.
If you intend to redefine the non-virtual function inherited from class B in Class D, the D object is likely to behave inconsistently. More specifically, any one D object may exhibit the behavior of B or D, and the determinant is not the object itself, but the "pointer to the object" that originally declared the type. References also show the same incomprehensible acts as pointers.
As already mentioned, public inheritance is a is-a relationship ( clause 32). * * Clause **34 said that declaring a non-virtual function in class would create a invariance (invariant) for the class, which overrides its specificity (specialization). Applying these two ideas to Class B and Class D and the Non-virtual function B::MF,
- For every thing that applies to a B object, it also applies to the D object. (is-a Relationship)
- The derived classes of B must inherit the interface and implementation of MF, since MF is a non-virtual function.
Now redefine in D mf
, there will be contradictions. 1, if D really need to re-implement mf
(different from B), then the is-a relationship is not established, because each d is no longer true, in this case, should not be inherited as public. 2, if D must be public to inherit B, and D has a need to achieve a different mf
, so long can not reflect the invariance override specificity; Since then it should be declared as a virtual function. 3, if each d is a B is true, and mf
can really reflect the invariance overrides the specificity of the nature, then D long need not be redefined mf
.
Regardless of the above view, the conclusion is the same: no case should redefine a basic non-virtual function.
As already known in clause 7, the destructor within the base class should be virtual; If you violate clause 7, you will violate these terms, because destructors are available for each class, even if you do not write them yourself.
Summarize
- Never redefine the inherited non-virtual function.
Article 37: Never redefine inherited default parameter values
In inheritance, only two functions can be inherited: virtual and non-virtual. In clause 36 we learned that an inherited non-virtual function cannot be redefined. This article deals with the problem of inheriting virtual functions, and more specifically: inheriting a virtual function with default parameter values.
We should know that the virtual function is dynamic bound (dynamically bound), and the default parameter value is static binding (statically bound).
The static type of an object is the type that it takes when it is declared in a program, such as
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=Green) const;//不同缺省参数值,很糟糕 ……};class Circle: public Shape{public: virtual void draw(ShapeColor color) const; /*客户调用上面函数时,如果使用对象调用,必须指定参数值,因为静态绑定下这个函数不从base继承缺省值。*/ /*如果使用指针或引用调用,可以不指定缺省参数值,动态绑定会从base继承缺省参数值*/ ……};
This inheritance is simple. Now use this
Shape* ps;Shape* pc=new Circle;Shape* pr=new Rectangle;
These pointer types are pointer-to-shape types and are static type shape*. The dynamic type of an object is "currently referred to as the object type." A dynamic type can show what behavior an object will have. The dynamic type of PC is CIRCLE*,PR dynamic type is rectangle*,ps no dynamic type (it does not point to any object). Dynamic types can be changed during execution, and re-assignment can change the dynamic type.
The virtual function is dynamically bound, and the code that invokes the function implementation depends on the dynamic type of the object being called.
pc->draw(Shape::Red);pr->draw(Shape::Red);
This invocation is justifiable, with parameter values. But what if we don't have the parameter values?
pr->draw();//调用Rectangle::draw(Shape::Red)
In the above call, the PR dynamic type is rectangle*, so call rectangle's virtual function. Rectangle: The default value of the:d raw function is green, but the PR is static type shape*, so the default parameter value for this call is from the shape class, not the Rectangle class. This time the call two functions each half of the force.
The reason why C + + uses this bizarre way of working is because of efficiency. If the default parameter value is dynamically bound, the compiler must have some way to determine the appropriate parameter default value for the virtual function at run time. This is slower and more complex than the current implementation of the "in compiler decision" mechanism. C + + has made such a trade-off in order to implement the speed and simplicity of the compiler implementation.
We try to follow this rule and provide the same parameter values for base class and derived class
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; ……};
Again, the code repeats with dependencies (with dependencies): If the default parameter value in shape changes, the value of the default parameter of the derived classes is changed, otherwise it will result in a duplicate default parameter value being defined.
If you do need the default parameter values for derived classes, then you need an alternative approach. clause 35 lists some alternatives to virtual functions, such as the NVI approach:
class Shape{public: enum ShapeColor{ Red, Green, Blue}; void draw(ShapeColor=Red) const { doDraw(color); } ……private: virtual void doDraw(ShapeColor color) const=0;//真正在这里完成工作};class Rectangle: public Shape{public: ……private: virtual void draw(ShapeColor color) const; ……};
Because the Non-virtual function is not derived ( clause 36), this design makes it clear that the value of the color default parameter of the draw function is always red.
Summarize
- Do not redefine an inherited default parameter value because the default parameter value is a static binding, and the virtual function (the only thing you should overwrite) is dynamic binding.
"Effective C + +": Clause 36-clause 37