76123


以下のように親クラス`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]

## 副題

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


CC BY-SA 4.0
2021-05-27T08:53:16.173
76111
2021-05-28T12:41:01.597
2021-05-27T14:39:00.257
40856
40856
question
c++
カスタムクラスのダウンキャストの方法がわからない


純粋仮想関数を使うことをお勧めします。純粋仮想関数は関数は定義しないものの、派生クラスにはメンバー関数が存在することを保証します。

[code]

    class Figure {
    protected:
        int width;
        int height;
    public:
        Figure(int width, int height){
            this->width = width;
            this->height = height;
        }
        virtual int getArea() = 0;
    };
    class Triangle : public Figure {
    public:
        Triangle(int width, int height) : Figure(width, height) {}
        int getArea() override
        {
            return width * height / 2;
        }
    };
    
    class Rectangle : public Figure{
    public:
        Rectangle(int width, int height) : Figure(width, height){}
        int getArea() override
        {
            return width * height;
        }
    };
    
[/code]

こうなっていれば、 `Figure::getArea()` はそのまま呼び出すことができます。

[code]

    for(Figure figure: figureList){
        cout << figure.getArea() << endl;
    }
    
[/code]

なお、 この記述では `figureList` の各要素が `figure` へコピーが発生します。 `Figure&` とすることをお勧めします。


CC BY-SA 4.0
2021-05-27T11:59:05.127
76119
2021-05-27T11:59:05.127
4236
76111
answer


## 2つの方法で出来ました

以下のようにmainを変更して、2つの方法でダウンキャストできました

## 問題点1

どちらの方法も`クラスの識別`をしていないので、なにをTriangle、Rectangleにすればいいのかがわからない

## 問題点2

クラスの識別ができればコード的には`安全`なのか?

[code]

    int main()
    {
        vector<Figure> figureList1{{
            Triangle(10, 3),
            Rectangle(10, 2),
        }};
    
        for (Figure& figure : figureList1)
        {
            cout << ((Rectangle *)&figure)->getArea() << endl;
        }
    
        vector<Figure*> figureList2{{ //ポインタにした
            new Triangle(10, 3),
            new Rectangle(10, 2),
        }};
    
        for (Figure* figure : figureList2)
        {
            // static_castでダウンキャスト
            cout << static_cast<Rectangle *>(figure)->getArea() << endl;
        }
    }
    
[/code]


CC BY-SA 4.0
2021-05-27T15:34:36.203
76121
2021-05-27T15:34:36.203
40856
76111
answer


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

[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`の後に該当の型をすべて並べないといけないので面倒です。


CC BY-SA 4.0
2021-05-27T15:40:46.630
76123
2021-05-28T12:41:01.597
2021-05-28T12:41:01.597
43506
43506
76111
answer


これは直接的な回答ではありませんが、C++ダウンキャストに関する根本的な誤解があるようにも見えます。

> 見つけたダウンキャスト方法  
>  ただこれだと`Rectangle`しかできない
[code]

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

ダウンキャスト(Downcast)は、基底クラス型(`Figure`)から派生クラス型(`Triangle`や`Rectangle`)というクラス階層の下方向へと型変換(Type
casting)を行います。あらゆる型変換において、 **変換先の型の明示** は避けられません。

つまり`Rectangle`型へ変換したいならソースコード上で`Rectangle`型を明示する必要があります。`Triangle`型やその他の型へも対応させたいなら、それぞれの型名がソースコード上に現れるのは必然です。

他回答で提示されているC++仮想関数(virtual function)は、利用時に **実際のオブジェクト型をソースコード上に明示することなく**
派生クラスのメンバ関数を呼び出せるという言語機能です。通常のクラス設計においては、仮想関数の利用をおすすめします。

* * *

> コメント：別の例での説明なのですが、基底クラス(Base)、派生クラス(A, B, C, D, E)があって、D,
> Eにはprint()という関数があって、A, B, Cにはprint()がないとした時にこの場合にもprint()を仮想関数にすべきでしょうか?

「クラス設計意図による」としか回答できません。

クラス設計として「`print()`メンバ関数を持つことを表現」するインタフェース(`IPrintable`)を用意する案も考えられます。

[code]

    struct Base { /*...*/ };
    struct IPrintable {
      virtual void print() = 0;
    };
    
    struct A : Base { /*...*/ };
    struct B : Base { /*...*/ };
    struct C : Base { /*...*/ };
    struct D : Base, IPrintable { /*...*/ };
    struct E : Base, IPrintable { /*...*/ };
    
[/code]


CC BY-SA 4.0
2021-05-28T06:51:42.200
76144
2021-05-28T09:10:31.137
2021-05-28T09:10:31.137
49
49
76111
answer