第 9 章: オーバーライド

オーバーライドとは

継承によって作られたサブクラスは, スーパークラスのメンバを引き継ぎるが, オーバーライドを使うと, 引き継いだメンバ関数の中身を書き換えることができる.

つまり, スーパークラス側とサブクラス側で, 異なる動作をさせることができる.

実際のプログラミング

pen.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// pen.h
// CColorPen クラスで使う線の色

#define BLACK (0)
#define RED (1)
#define BLUE (2)
#define GREEN (3)

// 黒いペンのクラス
class CPen{
 public:
  virtual void DrawLine(int sx, int sy, int ex, int ey); // 線を描く
};

// 色ペンのクラス
class CColorPen : public CPen{
 private:
  int m_color; // 線の色
  
 public:
  void DrawLine(int sx, int sy, int ex, int ey); // 線を描く
  void SetColor(int color); // 線の色を設定する
};

pen.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// pen.cpp
#include <iostream>
#include "pen.h"

// 黒い線を描く
void CPen::DrawLine(int sx, int sy, int ex, int ey){
  // 黒い線を描く
  std::cout << "黒い線を描く" << std::endl;
}

// 現在設定されている色で線を描く
void CColorPen::DrawLine(int sx, int sy, int ex, int ey){
  // m_color を参照して, その色で線を描く
  std::cout << "色のある線を描く" << std::endl;
}

// 線の色を設定する
void CColorPen::SetColor(int color){
  m_color = color;
}

main.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// main.cpp

#include "pen.h"

int main(){

  CPen pen;
  CColorPen color_pen;

  pen.DrawLine(10, 10, 100, 100); // 黒い線を描く
  
  color_pen.SetColor(RED); // 赤色を設定
  color_pen.DrawLine(20, 20, 200, 200); // 赤い線を描く

  return 0;
}

GNUmakefile

.SUFFIXES: .o .cpp
.cpp.o:
	$(CC) -c $(CFLAGS) $<

PROG = main
CC = g++
CFLAGS = -g -Wall
SRC = pen.cpp main.cpp
OBJ = pen.o main.o

hist: $(OBJ)
	$(CC) $(CFLAGS) -o $(PROG) $(OBJ)

.PHONY: clean
clean:
	$(RM) $(PROG) $(OBJ) *~ a.out

main.o: pen.h main.cpp
pen.o: pen.h pen.cpp

上記プログラムの実行結果は:

[wtopia drawline]$ make
g++ -c -g -Wall pen.cpp
g++ -c -g -Wall main.cpp
g++ -g -Wall -o main pen.o main.o
[wtopia drawline]$ ./main
黒い線を描く
色のある線を描く

注意:

オーバーライドを行うために, スーパークラス側のメンバ関数 [virtual] というキーワードを付ける.
[virtual] をつけて宣言されたメンバ関数のことを, [仮想関数] と呼ぶ.

多態性

下記の二つのクラスを定義する:

1. 直線を描くクラス (スーパークラス)
2. 点線を描くクラス (サブクラス)

いずれもペンであることに変わらない, [線を描く] という共通動作を持っている:

// 直線を描くペン
class CBasicPen{
public:
  // 線を描く
  virtual void DrawLine(int sx, int sy, int ex, int ey);
};

// 点線を描く
class CDotPen : public CBasicPen{
public:
  // 線を描く
  virtual void DrawLine(int sx, int sy, int ex, int ey);
};

サブクラス (点線) はスーパークラス (直線) でもある, A is a B の関係:

CBasicPen *array[3];
CBasicPen pen1;
CBasicPen pen2;

CDotPen pen3;

array[0] = &pen1;
array[1] = &pen2;
array[2] = &pen3; // CDotPen は CBasicPen でもある

本質的には異なるペンであっても, ペンであることは変わらないことで, 一つの配列にまとめた.

多状性 (ポリモルフィズム) は, C++ 的に言えば, 同じメンバ関数であっても, それを呼び出すインスタンスの違いによって, 実際に呼び出させるメンバ関数を自動的に変える性質.

この性質により, 見た目の上では CBasicPen 型の配列からメンバ関数を呼び出すように見えても, 実際には, その配列に格納されているポインタが指しているインスタンスのメンバ関数を呼び出してくれる:

CBasicPen *array[3];

CBasicPen pen1;
CBAsicPen pen2;

CDotPen pen3;

array[0] = &pen1;
array[1] = &pen2;
array[2] = &pen3; // CDotPen は CBasicPen でもある

for ( i = 0; i < 3; i++){
  // CBasicPen::DrawLine か CDotPen::DrawLine を呼び出す
  array[i]->DrawLine(10+i, 10+i, 100+i, 100+i);
}

注意:

array[0] と arrya[1] に CBasicPen クラスインスタンスへのポインタが格納されるので, この部分では CBasicPen::DrawLine() が呼び出される.
array[2] に CDotPen クラスインスタンスへのポインタが格納されるので, CDotPen::DrawLine() が呼び出される.

多態性には:

メンバ関数がオーバーライドされており, スーパークラス型の変数がポインタである必要がある.

クラスのサイズ

クラスには, メンバ関数を追加しても, サイズは変わらない.

仮想関数を一つでも含んでいるクラスは, コンパイラが暗黙のうちに, 多態性を実現するために必要な情報を埋め込む. この情報のため, クラスのサイズは増加する.