当記事はダウンキャストについての知識が前提となりますので、知らない方は下記の記事を先に読むことをお勧めします。
https://qiita.com/maihei541/items/cff2e66540f83aacd213#comment-402aa67ebd4ac1e28147
dynamic_castでダウンキャストするとエラーになる
安全にダウンキャストを行うには、dynamic_cast
を活用するのでしたね。
とりあえずそれっぽくコードを書いてみます。
#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;
}
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に生のポインタを渡してみる。
#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;
}
Sub1
Sub2
1
続行するには何かキーを押してください . . .
一見成功したように見えました。
しかしこのまま続行すると
ブレークポイント命令 (__debugbreak() ステートメントまたは類似の呼び出し) が dynamic_cast.exe で実行されました。
エラー箇所
//
// 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
のダウンキャストを行うことが可能です。
#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;
}
Sub1
Sub2
1
続行するには何かキーを押してください . . .
正しく実行され、この方法なら参照カウンタが正しく動作するため、このまま続行したり、解放が行われても問題が起こることはありません。
総括
・スマートポインタに対してdynamic_cast
をそのまま行うと危険な動作が行われてしまう。
・shared_ptr
に確保した派生クラスをダウンキャストする際はdynamic_pointer_cast
を使用しよう。