読者です 読者をやめる 読者になる 読者になる

C++11におけるoverride、final、default、delete指定子の必要性と有用性

 C++11では、コードの生産性、保守労力の軽減、学びやすさ等に主眼が置かれ仕様が決定されましたが、ここではoverride指定子の有用性について考えてみたいと思います。
 override指定子は、あるサブクラスの仮想メンバー関数が、そのサブクラスのスーパークラスの仮想メンバー関数を「同じ型と同じ関数名でオーバーライドしているはずの」関数であることをコンパイラに知らせるためのものなのですが、純粋仮想関数ではないスーパークラスの関数をオーバーライドする時のミスを減らしたい場合、有用だと思いました。
 それは、スーパークラスの仮想関数をサブクラスでオーバーライドしたつもりが、返り値の型、引数の型、その他指定子が微妙に違っており、コンパイルエラーは出ておらず実行ファイルも起動するのに、目的の動作にならないという場合です。

class Super
{
public:
	Super(){}
	virtual ~Super(){}

	virtual void set_int(int _val)
	{
		data = _val;
	}
private:
	int data;
};

class Sub : public Super
{
public:
	Sub(){}
	virtual ~Sub(){}

	virtual void set_int(short _val)
	{
		sub_data = _val;
	}
private:
	int sub_data;
};

 上記の場合、引数の型が違うので、Super::set_intはオーバーライド出来ていないにも関わらず、エラーは出ず、プログラマは実行して異常に気づくまで放置することになります。しかし、Sub::set_intにoverride指定子を付けておくことにより、Superに同一の型の仮想関数がないことをコンパイラに知らせることが出来、コンパイルエラーを出してくれます。
 以下のように、オーバーライドしているつもりだけど出来ていない部分にoverride指定子を付けます。

class Super
{
public:
	Super(){}
	virtual ~Super() = 0;

	virtual void set_int(int _val)
	{
		data = _val;
	}
private:
	int data;
};

class Sub : public Super
{
public:
	Sub(){}
	virtual ~Sub(){}

	virtual void set_int(short _val) override
	{
		sub_data = _val;
	}
private:
	int sub_data;
};

 すると、オーバーライドする関数の型がスーパークラスのものと違うため、以下の様なエラーが出ます。

error C3668: 'Sub::set_int' : method with override specifier 'override' did not override any base class methods

 ここでは引数の型が異なるためにエラーとなっていますが、関数名や帰り値、指定子が異なってもエラーとなります。因みに下記のように、メンバー関数の定義部分にoverride指定子は書けません。エラーが出ます。

class Super
{
public:
	Super(){}
	virtual ~Super() = 0;

	virtual void set_int(int _val) = 0;
	/*{
		data = _val;
	}*/
private:
	int data;
};

class Sub : public Super
{
public:
	Sub(){}
	virtual ~Sub(){}

	virtual void set_int(short _val) override;
private:
	int sub_data;
};

void Sub::set_int(short _val) override
{
	sub_data;
}

 この様な小さなテストコードでは何がありがたいのかよくわからないかもしれませんが、オーバーライドを多用するようなソースを大量に書く場合、わかりづらいバグの混入確率が上がると考えられ、そのようなバグを発見して修正する時間もバカにならないので、これを未然に防げるというのは大きいです。
 例えば、アブストラクトクラスを定義して、それを継承して使うというケースは、C++使いであれば当たり前の様に存在するでしょう。その場合、アブストラクトクラスに宣言されている型の関数をサブクラスでオーバーライドして使うことが目的なので、オーバーライドしているつもりが、実は型の違う別の関数を定義してしまっていて、コンパイルエラーも出なかったので発見が遅れたなどというケースは十分考えられますね。
 サブクラスの、「オーバーライドしているつもり」の関数の宣言時に、上記のようにoverride指定子を付けるだけでこれが防げます。なかなかいいですね。

 override指定子に絡んで、final指定子というものも用意されました。これはoverride指定子とは逆で、スーパークラスが継承されないことと、スーパークラスの仮想関数がオーバーライドされないことを指定します。指定子を付ける箇所も、スーパークラスの関数宣言部分に付けます。オーバーライドされる側とする側から、関数の型の不一致を防げる様になっているわけですから、積極的に使うことでコーディングの安全性を高めることに役立つことは間違いありません
 下記の様に、スーパークラスの仮想関数にfinal指定子を付けることで、サブクラスでのオーバーライドをエラー化出来ます。

class Super
{
public:
	Super(){}
	virtual ~Super() = 0;

	virtual void set_int(int _val) final = 0;

private:
	int data;
};

class Sub : public Super
{
public:
	Sub(){}
	virtual ~Sub(){}

	virtual void set_int(int _val) override
	{
	}
private:
	int sub_data;
};

 また、継承できないクラスを作るには以下のようにします。

class Super final
{
public:
	Super(){}
	virtual ~Super() = 0;

	virtual void set_int(int _val) = 0;

private:
	int data;
};

class Sub : public Super
{
public:
	Sub(){}
	virtual ~Sub(){}

	virtual void set_int(int _val) override
	{
	}
private:
	int sub_data;
};

 次に、default指定子ですが、この指定子は単純にデフォルトコンストラクタを指定します。具体的には以下のように使います。

class Super
{
public:
	Super() = default;
	virtual ~Super() = 0;

	virtual void set_int(int _val) = 0;
private:
	int data;
};

 普通にデフォルトコンストラクタの定義を書かなくていいのでスッキリして便利ですが、この記述ではメンバー変数の初期化が出来ません。C++においてメンバー変数を初期化しないということはあまりないので、使いドコロがいまいちわかりません。
 
 最後にdelete指定子ですが、これはクラス内において、宣言、定義、呼び出しがどれも出来ない関数の型を指定できます。以下のように使います。

class Foo
{
public:
	Foo() = default;
	~Foo(){}

	int getid(void) = delete;

private:
	int data;
};

 上記のFoo::getid関数を呼びだそうとすると以下の様なエラーが出ます。

error C2280: 'int Foo::getid(void)' : attempting to reference a deleted function

 定義されてない、というエラーではなく、delete指定子が付けられたものを呼びだそうとしているという明示的なエラーになっています。正直これがないから困るとは思いませんが、コンパイル時間の短縮にでもなっているのでしょうか。

 C++11に関してはまだまだ知識が適当すぎるので、C++11に移行する場合はもう少し調査が必要でしょう。私自身はC++11前の言語仕様でも大して文句はないのですが、周りがC++11に移行しつつあるようであれば、知っておくべき肝はまだありそうです。