読書ノートEffective_C++_条項三十七:継承されたデフォルトパラメータ値は再定義されません.
12424 ワード
まず次の例を見てみましょう.
疑問符の出力は何ですか?
この質問に答えるには、虚関数の知識を振り返る必要があります.親に虚関数が存在する場合、コンパイラは虚表と虚ポインタを生成し、プログラムの実行時に虚ポインタの指向に基づいて、どの虚関数を呼び出すかを決定します.これを動的バインドと呼び、これに対して静的バインドであり、静的バインドはコンパイル期間で決定されます.
動的バインドを実現する代価は比較的大きいので,コンパイラは関数パラメータの部分で動的バインド方式を採用していない,すなわち,デフォルトのパラメータは静的バインドであり,コンパイル期間で決定される.
この2行のコードを見て、分析してみましょう.
Srの静的タイプはShape*であり、動的タイプこそRectangle*であり、同様にstの静的タイプはShape*であり、動的タイプはTriangle*である.ここではパラメータが付いていないので、デフォルトのパラメータ、すなわち静的Shape::Draw()のデフォルト値REDを使用しているので、両方の問題の出力値は0です.
コンパイラはパラメータにダイナミックバインドを使用していないため、継承された虚関数に異なるデフォルト値を使用すると、読者に大きな困惑をもたらします.次の2行のコードを考えてみてください.
虚関数のデフォルト値を必ず使用する場合は、親に設定すればよいので、条項35でいうNVIメソッドを借りることができます.以下のようにします.
前述の条項ではnon-virtual関数は上書きされないことが約束されているので、サブクラスで異なるデフォルトのパラメータ値を定義する問題が発生する心配はありません.
最後にまとめます.
デフォルトのパラメータ値は静的バインドであり、virtual関数は上書きすべき唯一のものであり、動的バインドであるため、継承されたデフォルトのパラメータ値を再定義しないでください.
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関数は上書きすべき唯一のものであり、動的バインドであるため、継承されたデフォルトのパラメータ値を再定義しないでください.