カスタムクラスのダウンキャストの方法がわからない


以下のように親クラス`Figure`の`vector`に子クラス`Triangle`、`Rectangle`を入れています。

その際に`Triangle`、`Rectangle`にあって`Figure`にはないメソッド`getArea()`をvectorの要素から呼び出しています。

それをすると`Figure`の`vector`に`getArea()`はないというエラーが出てしまいます。  
それはもっともなのですが、ダウンキャストをしたいのですが、どうすればよいでしょうか？

## 見つけたダウンキャスト方法

ただこれだと`Rectangle`しかできない

[code]

    //エラー部分
    cout << ((Rectangle *)&figure)->getArea() << endl;
    
[/code]

## できそうな方法

これらは見つけましたが、実装方法がわかりません。

  * dynamic_cast
  * static_cast

[code]

    // どちらもエラーになる
    cout << dynamic_cast<Rectangle *>(figure).getArea() << endl;
    cout << static_cast<Rectangle *>(figure).getArea() << endl;
    
[/code]

## プログラム全体

[code]

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Figure
    {
    protected:
        int width;
        int height;
    
    public:
        Figure(int width, int height)
        {
            this->width = width;
            this->height = height;
        }
    };
    class Triangle : public Figure
    {
    public:
        Triangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height / 2;
        }
    };
    
    class Rectangle : public Figure
    {
    public:
        Rectangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height;
        }
    };
    
    class Line : public Figure
    {
    public:
        Line(int width, int height) : Figure(width, height) {}
        int getWidth(){
            return this->width;
        }
    };
    
    int main()
    {
        Line line = Line(4, 2);
        cout << line.getWidth() << endl;
    
        vector<Figure> figureList{{
            Triangle(10, 3),
            Rectangle(10, 2),
        }};
    
        for (Figure& figure : figureList)
        {
            cout << figure.getArea() << endl; //エラー部分
        }
    }
    
[/code]

## 副題

  * ダウンキャストはできてもしない方がいいのでしょうか？




ダウンキャストを使うなら(メモリ解放は省略)

[code]

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Figure
    {
    protected:
        int width;
        int height;
    
    public:
        Figure(int width, int height)
        {
            this->width = width;
            this->height = height;
        }
        virtual ~Figure() {}
    };
    class Triangle : public Figure
    {
    public:
        Triangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height / 2;
        }
    };
    
    class Rectangle : public Figure
    {
    public:
        Rectangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height;
        }
    };
    
    int main()
    {
        vector<Figure*> figureList{ {
            new Triangle(10, 3),
            new Rectangle(10, 2),
        } };
    
        for (auto figure : figureList)
        {
            if (auto t = dynamic_cast<Triangle*>(figure))
                cout << t->getArea() << endl;
            else if (auto r = dynamic_cast<Rectangle*>(figure))
                cout << r->getArea() << endl;
        }
    }
    
[/code]

`dynamic_cast`は、指定したポインタ型にキャストできないと`nullptr`になるので、それを`if`文で判定して呼び分けることができます。  
ただ、該当の型すべてについて`if`文を書かなければならないので面倒です。  
なお、親のクラスに仮想関数がないと`dynamic_cast`が使えないので、`Figure`に仮想デストラクタを追加しています。

C++17が使えるなら、こんな方法もあります。

[code]

    #include <iostream>
    #include <vector>
    #include <variant>
    
    using namespace std;
    
    class Figure
    {
    protected:
        int width;
        int height;
    
    public:
        Figure(int width, int height)
        {
            this->width = width;
            this->height = height;
        }
    };
    class Triangle : public Figure
    {
    public:
        Triangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height / 2;
        }
    };
    
    class Rectangle : public Figure
    {
    public:
        Rectangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height;
        }
    };
    
    int main()
    {
        vector<variant<Triangle, Rectangle>> figureList{ {
            Triangle(10, 3),
            Rectangle(10, 2),
        } };
    
        for (auto& figure : figureList)
        {
            cout << visit([](auto& x) { return x.getArea(); }, figure) << endl;
        }
    }
    
[/code]

こちらも、`variant`の後に該当の型をすべて並べないといけないので面倒です。




ダウンキャストを使うなら(メモリ解放は省略)

[code]

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Figure
    {
    protected:
        int width;
        int height;
    
    public:
        Figure(int width, int height)
        {
            this->width = width;
            this->height = height;
        }
        virtual ~Figure() {}
    };
    class Triangle : public Figure
    {
    public:
        Triangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height / 2;
        }
    };
    
    class Rectangle : public Figure
    {
    public:
        Rectangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height;
        }
    };
    
    int main()
    {
        vector<Figure*> figureList{ {
            new Triangle(10, 3),
            new Rectangle(10, 2),
        } };
    
        for (auto figure : figureList)
        {
            if (auto t = dynamic_cast<Triangle*>(figure))
                cout << t->getArea() << endl;
            else if (auto r = dynamic_cast<Rectangle*>(figure))
                cout << r->getArea() << endl;
        }
    }
    
[/code]

`dynamic_cast`は、指定したポインタ型にキャストできないと`nullptr`になるので、それを`if`文で判定して呼び分けることができます。  
ただ、該当の型すべてについて`if`文を書かなければならないので面倒です。  
なお、親のクラスに仮想関数がないと`dynamic_cast`が使えないので、`Figure`に仮想デストラクタを追加しています。

C++17が使えるなら、こんな方法もあります。

[code]

    #include <iostream>
    #include <vector>
    #include <variant>
    
    using namespace std;
    
    class Figure
    {
    protected:
        int width;
        int height;
    
    public:
        Figure(int width, int height)
        {
            this->width = width;
            this->height = height;
        }
    };
    class Triangle : public Figure
    {
    public:
        Triangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height / 2;
        }
    };
    
    class Rectangle : public Figure
    {
    public:
        Rectangle(int width, int height) : Figure(width, height) {}
        int getArea()
        {
            return width * height;
        }
    };
    
    int main()
    {
        vector<variant<Triangle, Rectangle>> figureList{ {
            Triangle(10, 3),
            Rectangle(10, 2),
        } };
    
        for (auto& figure : figureList)
        {
            cout << visit([](auto& x) { return x.getArea(); }, figure) << endl;
        }
    }
    
[/code]

こちらも、`variant`の後に該当の型をすべて並べないといけないので面倒です。

