18.10 Dynamic casting


https://www.learncpp.com/cpp-tutorial/dynamic-casting/
レッスン8.5--
鋳造の概念を議論した.
このレッスンではdynamiccastというもう一つのタイプを紹介します

The need for dynamic_cast


マルチステート性を処理する場合、ベースクラスポインタを持つときに派生クラスの情報にアクセスしたいことがよくあります.
次に例を示します
#include <iostream>
#include <string>

class Base
{
protected:
	int m_value{};

public:
	Base(int value)
		: m_value{value}
	{
	}

	virtual ~Base() = default;
};

class Derived : public Base
{
protected:
	std::string m_name{};

public:
	Derived(int value, const std::string& name)
		: Base{value}, m_name{name}
	{
	}

	const std::string& getName() const { return m_name; }
};

Base* getObject(bool returnDerived)
{
	if (returnDerived)
		return new Derived{1, "Apple"};
	else
		return new Base{2};
}

int main()
{
	Base* b{ getObject(true) };

	// how do we print the Derived object's name here, having only a Base pointer?

	delete b;

	return 0;
}
上記のプログラムでは、getObject関数は常にBase pointerを返します.ただしpointerはBaseまたはDerived objectを指すことができる.この場合、ポインタがDelived objectを指している場合、Delived::getName()を呼び出すにはどうすればいいですか?
1つの方法はvirtual functionを使用することです
しかし、ベースオブジェクトを指すベースポイントについては、どうすればいいのでしょうか.
これは意味がない.ベースクラスを汚染する可能性があります
C++でDerived pointerをBase pointerに暗黙的に変換することを知っている.このプロセスをupcastingと呼ぶ
しかし、ベースポインタをDerivedポインタに変換する方法があればどうなるのでしょうか.では、Derived::getName()を直接呼び出すことができます.仮想化

dynamic_cast


c++はcastingオペレータでdynamic castをサポートします
これはこの目的に使用できます.
ダイナミック鋳造にはいくつかの異なる機能がありますが、ダイナミック鋳造の最も一般的な用途はBaseクラスポインタをDerivedクラスポインタに変換することです.
これはupcastingという反意語downscastです
dynamic castはstatic castのように使用できます
ここに例があります
int main()
{
	Base* b{ getObject(true) };

        Derived* d{ dynamic_cast<Derived*>(b) }; // use dynamic cast to convert Base pointer into Derived pointer

        std::cout << "The name of the Derived is: " << d->getName() << '\n';

	delete b;

	return 0;
}
getObjectを受信したBase pointerはDerived objectを指す
その後dynamic castで再びDerived pointerを使用して降格します

dynamic_cast failure


上記の例は正常に動作しています.bobjectはDerived Objectへのポインタなので
しかし、そうでなければ、どうなるのでしょうか.
これにより、getObjectのパラメータをfalseに簡単に変換してテストすることができます.
このようなdynamic castを試してみると失敗するかもしれません
dynamic castに失敗するとnull pointerになります
したがって、次のコードを記述する必要があります.
int main()
{
	Base* b{ getObject(true) };

	Derived* d{ dynamic_cast<Derived*>(b) }; // use dynamic cast to convert Base pointer into Derived pointer

	if (d) // make sure d is non-null
		std::cout << "The name of the Derived is: " << d->getName() << '\n';

	delete b;

	return 0;
}
if(d)で空のポインタであるかどうかをチェックすると、d->getName()へのアクセスが許可されます.

Rule


Always ensure your dynamic casts actually succeeded by checking for a null pointer result.
dynamiccastは、実行時に一貫性チェック(変換可能性を確保)を行い、パフォーマンスが低下します.
また、ここではdynamic castによるダウンロードに失敗する場合もあります.
  • With protected or private inheritance.
  • For classes that do not declare or inherit any virtual functions (and thus don’t have a virtual table).
  • In certain cases involving virtual base classes (see this page for an example of some of these cases, and how to resolve them).
  • Downcasting with static_cast


    Downcastingはstatic castであってもよい.最大の違いはstatic castの場合、ランタイムタイプをチェックしないことです.これはstatic castの使用が速いことを意味しますが、危険にもなります.
    ベースポインタがDerived objectを指していなくても、BasepointerからDerived pointerへの変換に成功します.これは私たちの何気ない動作です.
    そこで、static castでDowncastingを行いたい場合は、まずDerived class objectを指すかどうかを確認します.
    次の例ではvirtual functionを使用して確認します.
    #include <iostream>
    #include <string>
    
    // Class identifier
    enum class ClassID
    {
    	base,
    	derived
    	// Others can be added here later
    };
    
    class Base
    {
    protected:
    	int m_value{};
    
    public:
    	Base(int value)
    		: m_value{value}
    	{
    	}
    
    	virtual ~Base() = default;
    	virtual ClassID getClassID() const { return ClassID::base; }
    };
    
    class Derived : public Base
    {
    protected:
    	std::string m_name{};
    
    public:
    	Derived(int value, const std::string& name)
    		: Base{value}, m_name{name}
    	{
    	}
    
    	const std::string& getName() const { return m_name; }
    	virtual ClassID getClassID() const { return ClassID::derived; }
    
    };
    
    Base* getObject(bool bReturnDerived)
    {
    	if (bReturnDerived)
    		return new Derived{1, "Apple"};
    	else
    		return new Base{2};
    }
    
    int main()
    {
    	Base* b{ getObject(true) };
    
    	if (b->getClassID() == ClassID::derived)
    	{
    		// We already proved b is pointing to a Derived object, so this should always succeed
    		Derived* d{ static_cast<Derived*>(b) };
    		std::cout << "The name of the Derived is: " << d->getName() << '\n';
    	}
    
    	delete b;
    
    	return 0;
    }
    面倒に考えたくないならdynamic castをそのまま使いましょう

    dynamic_cast and references


    上記のすべての例はpointerのdynamic castingに表示されます(より一般的です).
    dynamiccastはreferenceにも使用できます.これはdynamic castがポインタと一緒に動作する方法に似ています
    #include <iostream>
    #include <string>
    
    class Base
    {
    protected:
    	int m_value;
    
    public:
    	Base(int value)
    		: m_value{value}
    	{
    	}
    
    	virtual ~Base() = default;
    };
    
    class Derived : public Base
    {
    protected:
    	std::string m_name;
    
    public:
    	Derived(int value, const std::string& name)
    		: Base{value}, m_name{name}
    	{
    	}
    
    	const std::string& getName() const { return m_name; }
    };
    
    int main()
    {
    	Derived apple{1, "Apple"}; // create an apple
    	Base& b{ apple }; // set base reference to object
    	Derived& d{ dynamic_cast<Derived&>(b) }; // dynamic cast using a reference instead of a pointer
    
    	std::cout << "The name of the Derived is: " << d.getName() << '\n'; // we can access Derived::getName through d
    
    	return 0;
    }
    c++はnull参照がないため、変換に失敗するとstd::bad castが返されます.

    dynamic_cast vs static_cast


    新しいプログラマーは、式static castとdynamic castの使用に戸惑うことがあります.
    答えは簡単です.downcastでない場合はstatic castを使用します.
    downcastの場合、通常dynamiccastが適切です.
    しかし、仮想迂回の方法も考えなければならない.

    Downcasting vs virtual functions


    一部の開発者はdynamic castが悪いクラス設計を表していると信じています
    代わりにvirtual functionを使います
    通常、downcastingよりvirtualfunctionの方が人気があります
    しかし、downcastingがより良い選択である場合もあります.
  • When you can not modify the base class to add a virtual function (e.g. because the base class is part of the standard library)
  • When you need access to something that is derived-class specific (e.g. an access function that only exists in the derived class)
  • When adding a virtual function to your base class doesn’t make sense (e.g. there is no appropriate value for the base class to return). Using a pure virtual function may be an option here if you don’t need to instantiate the base class.