仮想関数とオーバーライド

クラスには継承という機能があります。親クラスのメソッドを継承クラスに応じて変えたい場合が多々あります。そのような状況で仮想関数とオーバーライドを使用します。仮想関数とオーバーライドにより、継承のありがたみがわかるのできちんと使えるようになっておきましょう。

仮想関数やオーバーライドがないと

いま、人クラス(people)と生徒クラス(student)を作るとします。人クラスにはageという整数型の変数があり、setAgeとgetAgeによりageの値を変更したり得ることができます。

この人クラスを継承して生徒クラスを作ります。ただし、生徒の年齢は18歳以下としたいので、setAgeの際18より大きい値の場合は強制的に18とします。

上記をコーディングしたものが以下のとおりです。

#include <iostream>
using namespace std;
 
class people{
protected:
    int age;
public:
    int getAge(){return age;}
	void setAge(int x){age=x;}
};

class student:public people{
public:
	void setAge(int x){
	    if(x>18)age=18;
		else age=x;
	}
};
 
int main(void){
    people *p;
	people p_ob;
	student s_ob;

	p = &p_ob;
	p->setAge(20);
	cout << p->getAge() << endl;
	
	p = &s_ob;
	p->setAge(20);
	cout << p->getAge() << endl;
	
    return 0;
}

解説

まず、

class people{
protected:
    int age;
public:
    int getAge(){return age;}
	void setAge(int x){age=x;}
};

により人クラスを定義しています。ageはprotectedにあるので、自分か継承したクラスしか使用できません。一方getAgeやsetAgeはpublicなので誰でも使用できます。次に生徒クラスです。

class student:public people{
public:
	void setAge(int x){
	    if(x>18)age=18;
		else age=x;
	}
};

生徒クラスには人クラスと同じsetAgeが定義されています。ただし、18歳より大きいと強制的に18になるようにしています。

生徒クラスと人クラスのオブジェクトをs_ob,p_obとして定義し、以下の命令により歳の設定と、表示をしています。

	p = &p_ob;
	p->setAge(20);
	cout << p->getAge() << endl;

20を人オブジェクトにセットして、表示しているので20が表示されます。

次に、ポインタを生徒オブジェクトに動かして、歳を20にセットし表示させています。

	p = &s_ob;
	p->setAge(20);
	cout << p->getAge() << endl;

生徒クラスをしようしており、18より20は大きいので18と表示してほしいところです。しかし、ここで表示されるのは20となります。生徒クラスのsetAgeではなく人クラスのsetAgeを使用しているためです。

20
20

仮想関数とオーバーロードの導入

先ほどのコードでは、生徒クラスに移動しても、setAgeは人クラスのものを使用していました。setAgeを生徒クラスのものを使用するようにするにはどうしたらいいでしょうか?

この場合は、仮想関数とオーバーロードを使用します。まず、

void setAge(int x){age=x;}

virtual void setAge(int x){age=x;}

とvirtualを付け加えます。これで、仮想関数となり、継承したクラスにこの関数が定義されていれば、上書き(オーバーロード)されます。

次に生徒クラスの

	void setAge(int x){
	    if(x>18)age=18;
		else age=x;
	}

の最後にoverrideとつけましょう。

	void setAge(int x) override{
	    if(x>18)age=18;
		else age=x;
	}

overrideはなくてもいいのですが、上書きすることをコンパイラへ教えるためにつけるようにしましょう(古いコンパイラの場合、overrideがないことがあります。その場合は、overrideを消してください。)。コードを書き直したものが以下のとおりです。

#include <iostream>
using namespace std;
 
class people{
protected:
    int age;
public:
    int getAge(){return age;}
	virtual void setAge(int x){age=x;}
};

class student:public people{
public:
	void setAge(int x) override{
	    if(x>18)age=18;
		else age=x;
	}
};
 
int main(void){
    people *p;
	people p_ob;
	student s_ob;

	p = &p_ob;
	p->setAge(20);
	cout << p->getAge() << endl;
	
	p = &s_ob;
	p->setAge(20);
	cout << p->getAge() << endl;
	
    return 0;
}

これを実行するときちんと狙い通りに動作してくれます。

20
18

純粋仮想関数

親クラスに仮想関数を宣言しておき、詳細は子クラスにお願いしたいことがよくあります。そのようなときに純粋仮想関数を使用します。もし、継承したクラスが純粋仮想関数の中身を定義しないとエラーが起きるようになります。構文は以下のように仮想関数の最後に=0を加えます。

virtual 戻り値 関数名(引数)=0;

では具体的なコードを以下に示します。この場合、setValueを生徒クラスが定義しないとエラーになります。

#include <iostream>
using namespace std;
 
class people{
protected:
    int age;
public:
    int getAge(){return age;}
	virtual void setAge(int x)=0;
};

class student:public people{
public:
	void setAge(int x) override{
	    if(x>18)age=18;
		else age=x;
	}
};
 
int main(void){
    people *p;
	student s_ob;
	
	p = &s_ob;
	p->setAge(20);
	cout << p->getAge() << endl;
	
    return 0;
}
18

著者:安井 真人(やすい まさと)