It is always wrong to redefine an inherited non-virtual function. The scope of this article is limited to "virtual function with default parameters ".
Virtual functions are dynamically bound, but the default parameter is static binding.
The so-called static type of an object is the type it uses when declared in a program.
Class shape {
Public:
Enum shapecolor {red, green, blue };
Virtual void draw (shapecolor color = Red) const = 0;
};
Class rectangle: Public shape {
Public:
// Assign different default parameters. This is really bad!
Virtual void draw (shapecolor color = green) const;
};
Class circle: Public shape {
Public:
Virtual void draw (shapecolor color) const;
// When the user calls this function as an object, the parameter value must be specified.
// This function does not inherit the default parameter value from its base under static binding.
// However, if you call this function using a pointer (or reference), you can leave the parameter value unspecified.
// Because the function is dynamically bound, it inherits the default parameter value from its base.
};
Shape * pS; // The static type is shape *
Shape * Pc = new circle; // The static type is shape *
Shape * Pr = new rectangle; // The static type is shape *
PS, PC, and PR no matter what these pointers point to, their static types are shape *
The so-called dynamic type of an object refers to the type of the object currently referred ". Can be changed during Program Execution
PS = Pr;
PS = pc;
Virtual functions are dynamically bound. This means that when a virtual function is called, which function implementation code is called depends on the dynamic type of the called object:
PC-> draw (shape: Red) // call circle: Draw
Pr-> draw (shape: Red) // call rectangle: Draw
When considering virtual functions with default parameters, because virtual functions are dynamically bound, the default parameters are statically bound. This means that when you call a virtual function defined in derived class, you can use base class to specify the default parameter value:
Pr-> draw (); // call rectangle: Draw (shape: Red )!
The dynamic type of PR is rectangle *, so the virtual function of rectangle is called. Rectangle: The default parameter of the draw function should be green, but because the PR static type is shape *, the default parameter value called comes from the shape class rather than the rectangle class!
Even if you replace the pointer with reference, the problem still exists.
Why does C ++ stick to this well-behaved approach? The answer is efficiency during the runtime. If the default parameter is dynamically bound, the compiler must have some way to determine the appropriate parameter default value for the virtual function at runtime. This is more slow and complex than the current mechanism of "determining during compilation. C ++ makes such a trade-off to make the execution speed of the program and the simplicity of the compiler implementation. The result is the execution efficiency you enjoy today.
What happens if you follow this rule and provide the default parameter value to the base and derived classes users?
Class shape {
Public:
Enum {red, green, blue };
Virtual void draw (shapecolor color = Red) const = 0;
};
Class rectangle: Public shape {
Public:
Virtual void draw (shapecolor color = Red) const;
};
Code already exists. Worse, there is dependency. If the default parameter value in the shape changes, all the derived classes that "repeat the default parameter value" must also change, otherwise, they will eventually lead to "repeatedly defining an inherited default parameter value ".
What should I do?
When you want to make virtual functions show the behavior you want, you have to worry about replacing the design. Clause 35. One of them is the nvi method:
Make a public non-virtual function in the base class call a private virtual function, which can be redefined by Derived classes. Here we can let the non-virtual function specify the default parameter, while the private virtual function is responsible for the real work:
Class shape {
Public:
Enum {red, green, blue };
Void draw (shapecolor color = Red) const
{
Dodraw (color );
}
PRIVATE:
Virtual void dodraw (shapecolor color) const = 0;
};
Class rectangle: Public shape {
Public:
PRIVATE:
Virtual void dodraw (shapecolor color) const; // you do not need to specify the default parameter.
};
Since the non-virtual function should never be overwritten by Derived classes (Clause 36), this design clearly makes the default color parameter value of the draw function always red.