読書ノートEffective_C++_条項三十七:継承されたデフォルトパラメータ値は再定義されません.

12424 ワード

まず次の例を見てみましょう.
 1 enum MyColor
 2 {
 3     RED,
 4     GREEN,
 5     BLUE,
 6 };
 7 
 8 class Shape
 9 {
10 public:
11     void virtual Draw(MyColor color = RED) const = 0;
12 };
13 
14 class Rectangle: public Shape
15 {
16 public:
17     void Draw(MyColor color = GREEN) const
18     {
19         cout << "default color = " << color << endl;
20     }
21 };
22 
23 class Triangle : public Shape
24 {
25 public:
26     void Draw(MyColor color = BLUE) const
27     {
28         cout << "default color = " << color << endl;
29     }
30 };
31 
32 
33 int main()
34 {
35     Shape *sr = new Rectangle();
36     Shape *st = new Triangle();
37     cout << "sr->Draw() = "; //
38     sr->Draw();
39     cout << "st->Draw() = "; //
40     st->Draw();
41     
42     delete sr;
43     delete st;
44 }

疑問符の出力は何ですか?
この質問に答えるには、虚関数の知識を振り返る必要があります.親に虚関数が存在する場合、コンパイラは虚表と虚ポインタを生成し、プログラムの実行時に虚ポインタの指向に基づいて、どの虚関数を呼び出すかを決定します.これを動的バインドと呼び、これに対して静的バインドであり、静的バインドはコンパイル期間で決定されます.
動的バインドを実現する代価は比較的大きいので,コンパイラは関数パラメータの部分で動的バインド方式を採用していない,すなわち,デフォルトのパラメータは静的バインドであり,コンパイル期間で決定される.
 
この2行のコードを見て、分析してみましょう.
1 Shape *sr = new Rectangle();
2 Shape *st = new Triangle();

Srの静的タイプはShape*であり、動的タイプこそRectangle*であり、同様にstの静的タイプはShape*であり、動的タイプはTriangle*である.ここではパラメータが付いていないので、デフォルトのパラメータ、すなわち静的Shape::Draw()のデフォルト値REDを使用しているので、両方の問題の出力値は0です.
コンパイラはパラメータにダイナミックバインドを使用していないため、継承された虚関数に異なるデフォルト値を使用すると、読者に大きな困惑をもたらします.次の2行のコードを考えてみてください.
1 Shape *sr = new Rectangle(); //  RED
2 Rectangle *rr = new Rectangle(); //  GREEN

虚関数のデフォルト値を必ず使用する場合は、親に設定すればよいので、条項35でいうNVIメソッドを借りることができます.以下のようにします.
 1 class Shape
 2 {
 3 public:
 4     void DrawShape(MyColor color = RED)
 5     {
 6         Draw(color);
 7     }
 8 private:
 9     virtual void Draw(MyColor color) const = 0
10     {
11         cout << "Shape::Draw" << endl;
12     }
13 };
14 
15 class Rectangle: public Shape
16 {
17 private:
18     void Draw(MyColor color) const
19     {
20         cout << "Rectangle::Draw" << endl;
21     }
22 };
23 
24 class Triangle : public Shape
25 {
26 private:
27     void Draw(MyColor color) const
28     {
29         cout << "Triangle::Draw" << endl;
30     }
31 };
32 
33 
34 int main()
35 {
36     Shape *sr = new Rectangle();
37     Shape *st = new Triangle();
38     cout << "sr->DrawRectangle() = "; // Rectangle::Draw
39     sr->DrawShape();
40     cout << "st->DrawTriangle() = "; // Triangle::Draw
41     st->DrawShape();
42     delete sr;
43     delete st;
44 }

前述の条項ではnon-virtual関数は上書きされないことが約束されているので、サブクラスで異なるデフォルトのパラメータ値を定義する問題が発生する心配はありません.
最後にまとめます.
デフォルトのパラメータ値は静的バインドであり、virtual関数は上書きすべき唯一のものであり、動的バインドであるため、継承されたデフォルトのパラメータ値を再定義しないでください.