1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ポインタで確保した派生クラスをダウンキャストする方法

Posted at

ダウンキャストはメモリのアクセス違反が起こる危険なキャストですが、C++の機能を使用することで安全にキャストを行うことができます。

そもそもダウンキャストとは

アップキャストしたポインタは親の関数のみを外部から使用できるため、安全なキャストでした。
最後まで親クラスにある関数のみで綺麗に記述出来たらよいのですが、開発を進めていく上で、どうしても派生クラスのみに関数を定義して、ポインタから使用したくなる時があります。
ダウンキャストを行えばそれが実現できますが、コードの安全面に問題があります。

down_cast.cpp
#include <iostream>
//基底クラス
class Super {
public:
	//クラス名を見るだけの純粋仮想関数
	virtual void ShowClassName() = 0;
};

//派生クラス1
class Sub1 : public Super {
private:
	int value = 1;
public:
	void ShowClassName() override {
		std::cout << "Sub1" << std::endl << std::endl;
	}
	//int型の値を表示するメソッド
	void ShowIntValue() {
		std::cout << value << std::endl << std::endl;
	}
};

//派生クラス2
class Sub2 : public Super {
private:
	float value = 1.0f;
public:
	void ShowClassName() override {
		std::cout << "Sub2" << std::endl << std::endl;
	}
	//float型の値を表示するメソッド
	void ShowFloatValue() {
		std::cout << value << std::endl << std::endl;
	}
};

//メイン関数
int main() {
	//アップキャストでSub1とSub2のインスタンスをSuper型のポインタに格納
	Super* sub1 = new Sub1();
	Super* sub2 = new Sub2();
	//ポリモーフィズムでSuper型のポインタからShowClassNameを呼び出す
	sub1->ShowClassName(); // 出力: Sub1
	sub2->ShowClassName(); // 出力: Sub2
	//派生クラスにしかないメソッドを呼びたい。
	//ダウンキャストでSuper型のポインタをSub1型に変換。
	Sub1* down_sub1 = (Sub1*)sub1;//sub1をSub1にダウンキャスト
	down_sub1->ShowIntValue(); // 出力: 1
	//危険なコードの例
	Sub2* down_sub2 = (Sub2*)sub1;//sub1をSub2にダウンキャスト
	down_sub2->ShowFloatValue(); //危険な関数呼び出し
	system("pause");
	return 0;
}
result
Sub1

Sub2

1

1.4013e-45

続行するには何かキーを押してください . . .

何が問題だったのか

コンソール結果が1.4013e-45となっていて、明らかに危険な呼び出しが行われたことが分かります。
これは、newで確保した型と別の型にキャストしていることが原因となります。

	//アップキャストでSub1とSub2のインスタンスをSuper型のポインタに格納
	Super* sub1 = new Sub1();
	//Super* sub2 = new Sub2();
	//ポリモーフィズムでSuper型のポインタからShowClassNameを呼び出す
	//sub1->ShowClassName(); // 出力: Sub1
	//sub2->ShowClassName(); // 出力: Sub2
	//派生クラスにしかないメソッドを呼びたい。
	//ダウンキャストでSuper型のポインタをSub1型に変換。
	//Sub1* down_sub1 = (Sub1*)sub1;//sub1をSub1にダウンキャスト
	//down_sub1->ShowIntValue(); // 出力: 1
	//危険なコードの例
	Sub2* down_sub2 = (Sub2*)sub1;//sub1をSub2にダウンキャスト
	down_sub2->ShowFloatValue(); //危険な関数呼び出し

インスタンスsub1が本来持たないはずの関数を呼び出しているために、危険な処理が実行されています。
危険なキャストが通らなければ良いのですが、従来のキャストだと、このような危険なコードが実行可能になってしまいます。

安全にダウンキャストを行うには

C++にはdynamic_castというものが存在しており、これを利用してキャストを行うことで従来よりも安全にダウンキャストを行うことができます。
まずは何も考えずに使ってみましょう。
dynamic_cast<キャスト後の型*>(キャストしたいインスタンス)で使用できます。

dynamic_cast1
#include <iostream>
//基底クラス
class Super {
public:
	//クラス名を見るだけの純粋仮想関数
	virtual void ShowClassName() = 0;
};

//派生クラス1
class Sub1 : public Super {
private:
	int value = 1;
public:
	void ShowClassName() override {
		std::cout << "Sub1" << std::endl << std::endl;
	}
	//int型の値を表示するメソッド
	void ShowIntValue() {
		std::cout << value << std::endl << std::endl;
	}
};

//派生クラス2
class Sub2 : public Super {
private:
	float value = 1.0f;
public:
	void ShowClassName() override {
		std::cout << "Sub2" << std::endl << std::endl;
	}
	//float型の値を表示するメソッド
	void ShowFloatValue() {
		std::cout << value << std::endl << std::endl;
	}
};

//メイン関数
int main() {
	//アップキャストでSub1とSub2のインスタンスをSuper型のポインタに格納
	Super* sub1 = new Sub1();
	Super* sub2 = new Sub2();
	//ポリモーフィズムでSuper型のポインタからShowClassNameを呼び出す
	sub1->ShowClassName(); // 出力: Sub1
	sub2->ShowClassName(); // 出力: Sub2
	//派生クラスにしかないメソッドを呼びたい。
	//ダウンキャストでSuper型のポインタをSub1型に変換。
	Sub1* down_sub1 = dynamic_cast<Sub1*>(sub1);//sub1をSub1にダウンキャスト
	down_sub1->ShowIntValue(); // 出力: 1
	//危険なコードの例
	Sub2* down_sub2 = dynamic_cast<Sub2*>(sub1);//sub1をSub2にダウンキャスト
	down_sub2->ShowFloatValue(); //危険な関数呼び出し
	system("pause");
	return 0;
}
result
例外がスローされました:読み取りアクセス違反。
this が nullptr でした。

33行目でnullptrのメンバを呼び出してしまったのでランタイムエラーが発生しました。
実は、dynamic_castでは、適切な型変換が行われなかった際に、nullptrとして処理されます。
この性質を利用して、適切に例外処理を行うことで安全にダウンキャストを行うことが出来そうです。

安全なコード

dynamic_cast2
#include <iostream>
//基底クラス
class Super {
public:
	//クラス名を見るだけの純粋仮想関数
	virtual void ShowClassName() = 0;
};

//派生クラス1
class Sub1 : public Super {
private:
	int value = 1;
public:
	void ShowClassName() override {
		std::cout << "Sub1" << std::endl << std::endl;
	}
	//int型の値を表示するメソッド
	void ShowIntValue() {
		std::cout << value << std::endl << std::endl;
	}
};

//派生クラス2
class Sub2 : public Super {
private:
	float value = 1.0f;
public:
	void ShowClassName() override {
		std::cout << "Sub2" << std::endl << std::endl;
	}
	//float型の値を表示するメソッド
	void ShowFloatValue() {
		std::cout << value << std::endl << std::endl;
	}
};

//メイン関数
int main() {
	//アップキャストでSub1とSub2のインスタンスをSuper型のポインタに格納
	Super* sub1 = new Sub1();
	Super* sub2 = new Sub2();
	//ポリモーフィズムでSuper型のポインタからShowClassNameを呼び出す
	sub1->ShowClassName(); // 出力: Sub1
	sub2->ShowClassName(); // 出力: Sub2
	//派生クラスにしかないメソッドを呼びたい。
	//ダウンキャストでSuper型のポインタをSub1型に変換。
	Sub1* down_sub1 = dynamic_cast<Sub1*>(sub1);//sub1をSub1にダウンキャスト
	//sub1にtry catch構文で例外処理を行う。
	try {
		if (down_sub1 == nullptr) {
			// 例外をスロー
			throw std::runtime_error("down_sub1のダウンキャストでエラーが発生しました!");
		}
		down_sub1->ShowIntValue(); // 出力: 1
	}
	catch (const std::runtime_error& e) {
		// 例外をキャッチして処理
		std::cerr << "例外キャッチ: " << e.what() << std::endl;
	}

	//sub2にtry catch構文で例外処理を行う。
	Sub2* down_sub2 = dynamic_cast<Sub2*>(sub1);//sub1をSub2にダウンキャスト
	try {
		if (down_sub2 == nullptr) {
			// 例外をスロー
			throw std::runtime_error("down_sub2のダウンキャストでエラーが発生しました!");
		}
		down_sub2->ShowFloatValue(); //危険な関数呼び出し
	}
	catch (const std::runtime_error& e) {
		// 例外をキャッチして処理
		std::cerr << "例外キャッチ: " << e.what() << std::endl;
	}

	system("pause");
	return 0;
}
result
Sub1

Sub2

1

例外キャッチ: down_sub2のダウンキャストでエラーが発生しました!
続行するには何かキーを押してください . . .

上記のコードのように、try catchdynamic_castの性質を利用することで、比較的安全にダウンキャストを行うことが可能になります。

総括

dynamic_castを使用することで、従来よりも安全にダウンキャストを行うことが可能でした。
・問題点として、ダウンキャストを多用してしまうと、処理が重くなる原因になったり、例外処理を何度も記述する必要性が出てくることがあります。
・上記の方法で比較的安全にダウンキャストを行えますが、積極的に使用していくというよりかはどうしても必要になってしまった場面に使用する程度の距離感が良いかと思います。

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?