2
0

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

当記事はダウンキャストについての知識が前提となりますので、知らない方は下記の記事を先に読むことをお勧めします。
https://qiita.com/maihei541/items/cff2e66540f83aacd213#comment-402aa67ebd4ac1e28147

dynamic_castでダウンキャストするとエラーになる

安全にダウンキャストを行うには、dynamic_castを活用するのでしたね。
とりあえずそれっぽくコードを書いてみます。

dynamic_cast1.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型のポインタに格納
	std::shared_ptr<Super> sub1 = std::make_shared<Sub1>();
	std::shared_ptr<Super> sub2 = std::make_shared<Sub2>();
	//ポリモーフィズムでSuper型のポインタからShowClassNameを呼び出す
	sub1->ShowClassName(); // 出力: Sub1
	sub2->ShowClassName(); // 出力: Sub2
	//派生クラスにしかないメソッドを呼びたい。
	//ダウンキャストでSuper型のポインタをSub1型に変換。
	Sub1* down_sub1 = dynamic_cast<std::shared_ptr<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;
	}

	system("pause");
	return 0;
}
error
E0695 : dynamic_cast 内の型は、完全なクラス型へのポインターまたは参照であるか、void * である必要があります。 47行目
C2680 : 'std::shared_ptr<Sub1,std::default_delete<Sub1>>': dynamic_cast のターゲット型が無効です。 47行目

エラーが出てしまいました。
std::shared_ptrなどのスマートポインタは、生のポインターというわけではなく、ポインタの所有権を持ち、*の演算子オーバーロードで、間接参照を行うクラスでした。
実は、dynamic_castは、生のポインタしか扱えないのです。

メンバ関数のget()でdynamic_castに生のポインタを渡してみる。

dynamic_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型のポインタに格納
	std::shared_ptr<Super> sub1 = std::make_shared<Sub1>();
	std::shared_ptr<Super> sub2 = std::make_shared<Sub2>();
	//ポリモーフィズムでSuper型のポインタからShowClassNameを呼び出す
	sub1->ShowClassName(); // 出力: Sub1
	sub2->ShowClassName(); // 出力: Sub2
	//派生クラスにしかないメソッドを呼びたい。
	//ダウンキャストでSuper型のポインタをSub1型に変換。
	std::shared_ptr<Sub1> down_sub1(dynamic_cast<Sub1*>(sub1.get()));//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;
	}

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

Sub2

1

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

一見成功したように見えました。
しかしこのまま続行すると

error
ブレークポイント命令 (__debugbreak() ステートメントまたは類似の呼び出し) が dynamic_cast.exe で実行されました。

エラー箇所

delete_scaler.cpp
//
// delete_scalar.cpp
//
//      Copyright (c) Microsoft Corporation. All rights reserved.
//
// Defines the scalar operator delete.
//
#include <crtdbg.h>
#include <malloc.h>
#include <vcruntime_new.h>
#include <vcstartup_internal.h>

////////////////////////////////////////////////////////////////
// delete() Fallback Ordering
//
// +-------------+
// |delete_scalar<----+-----------------------+
// +--^----------+    |                       |
//    |               |                       |
// +--+---------+  +--+---------------+  +----+----------------+
// |delete_array|  |delete_scalar_size|  |delete_scalar_nothrow|
// +--^----^----+  +------------------+  +---------------------+
//    |    |
//    |    +-------------------+
//    |                        |
// +--+--------------+  +------+-------------+
// |delete_array_size|  |delete_array_nothrow|
// +-----------------+  +--------------------+

_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
    #ifdef _DEBUG
    _free_dbg(block, _UNKNOWN_BLOCK);//ここでエラー
    #else
    free(block);
    #endif
}

どうやら、解放時にエラーが起こってしまうようです。
これは

std::shared_ptr<Sub1> down_sub1(dynamic_cast<Sub1*>(sub1.get()));//sub1をSub1にダウンキャスト

上記の方法では、参照カウンタが共有されないのです。

どうすればよかったのか

C++のstdにはdynamic_pointer_castという関数が存在しており、これを使用することで正常な参照カウンタでshared_ptrのダウンキャストを行うことが可能です。

dynamic_pointer_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型のポインタに格納
	std::shared_ptr<Super> sub1 = std::make_shared<Sub1>();
	std::shared_ptr<Super> sub2 = std::make_shared<Sub2>();
	//ポリモーフィズムでSuper型のポインタからShowClassNameを呼び出す
	sub1->ShowClassName(); // 出力: Sub1
	sub2->ShowClassName(); // 出力: Sub2
	//派生クラスにしかないメソッドを呼びたい。
	//ダウンキャストでSuper型のポインタをSub1型に変換。
	std::shared_ptr<Sub1> down_sub1 = std::dynamic_pointer_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;
	}

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

Sub2

1

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

正しく実行され、この方法なら参照カウンタが正しく動作するため、このまま続行したり、解放が行われても問題が起こることはありません。

総括

・スマートポインタに対してdynamic_castをそのまま行うと危険な動作が行われてしまう。
shared_ptrに確保した派生クラスをダウンキャストする際はdynamic_pointer_castを使用しよう。

2
0
0

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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?