基本クラスから派生クラスにキャストするよりも良い方法

StackOverflow https://stackoverflow.com/questions/412344

  •  03-07-2019
  •  | 
  •  

質問

このようなダウンキャスティングは機能しません。動作するメソッドが必要です。私の問題は次のとおりです。基本クラスから派生したいくつかの異なるクラスがあります。私の最初の試みは、基本クラスの配列を作成することでした。プログラムは、異なる派生クラスを(多かれ少なかれランダムに)選択する必要があります。基本クラスから派生クラスにキャストして、それを基本の配列に入れようとしましたが、明らかにうまくいきませんでした。派生クラスが非常に多く存在する可能性があるため、すべての派生クラスの配列を単に貼り付ける以外の方法を心から望んでいました。これを行うより良い方法はありますか?

すべてのコード例または詳細情報が必要な場合は、お知らせください。それはすべて私には理にかなっていますが、それは遅れており、他の人には意味がないかもしれませんね

ご協力ありがとうございます。

役に立ちましたか?

解決

意味がわかりません。値ごとにオブジェクトを保存し、 Base の配列を持っているように聞こえます。 Derivedを割り当てるとすぐに、そのオブジェクトがBaseに変換され、オブジェクトのDerived部分がスライスされるため、これは機能しません。しかし、ベースへのポインタの配列が必要だと思います:

Base * bases[NUM_ITEMS];
for(int i=0; i<NUM_ITEMS; i++) {
    int r = get_random_integer();
    if(r == 0)
        bases[i] = new Derived1;
    else if(r == 1)
        bases[i] = new Derived2;
    // ...
}

ポインターを使用したことがある場合は、ポインターを削除してメモリーを解放し、デストラクタを呼び出す必要があるため、ポインターを管理して、特にパススルーして失わないようにするのが苦痛であることがわかりますオブジェクト。 shared_ptrを使用すると、それを管理できます:

shared_ptr<Base> bases[NUM_ITEMS];
for(int i=0; i<NUM_ITEMS; i++) {
    int r = get_random_integer();
    if(r == 0)
        bases[i].reset(new Derived1);
    else if(r == 1)
        bases[i].reset(new Derived2);
    // ...
}

今、別のshared_ptrに bases [x] を渡すことができます。複数の参照があることに気付くでしょう-オブジェクトへの最後の参照がなくなると、自動的にdeleteを呼び出します範囲。理想的には、生の配列をstd :: vector:

に置き換えることもできます。
std::vector< shared_ptr<Base> > bases;
for(int i=0; i<NUM_ITEMS; i++) {
    int r = get_random_integer();
    if(r == 0)
        bases.push_back(shared_ptr<Base>(new Derived1));
    else if(r == 1)
        bases.push_back(shared_ptr<Base>(new Derived2));
    // ...
}

その後、ベクトルを渡すことができ、サイズを失うことなく、オンデマンドで動的にアイテムを追加できます。 bases.size()を使用してベクターのサイズを取得します。 shared_ptr こちら。

基本クラスから派生クラスへの変換は、絶対に必要な場合にのみ行う必要があります。通常、 polymorphism と呼ばれる手法を使用します。つまり、ベースポインターで関数を呼び出しますが、実際には同じシグネチャ(名前とパラメーター)を持つ派生クラスで定義された関数を呼び出します。は同じタイプです)オーバーライドと言われています。ウィキペディアの記事を読んでください。本当にキャストする必要がある場合は、生のポインタに対して次のように実行できます。

Derived1 * d = &dynamic_cast<Derived1&>(*bases[x]);

dynamic_castを使用すると、間違った型にキャストすると(つまり、キャストした型が作成されてベースポインターに割り当てられた型ではない場合)、オペレーターによってスローされる例外が発生します。 shared_ptrの場合、次の方法もあります。

shared_ptr<Derived1> d = dynamic_pointer_cast<Derived1>(bases[x]);
if(d) {
    // conversion successful, it pointed to a derived. d and bases[x] point still 
    // to the same object, thus share it. 
}

他のヒント

基本クラスから派生クラスへのキャストは、ほとんどの状況で悪臭を放ちます。

多態性は、仮想メソッドを使用することにより、より良いアプローチです。

呼び出し元のコードは、基本クラスポインターを使用してメソッドを呼び出し、派生クラスの適切な実装にディスパッチできるようにする必要があります。

これはかなり一般的な答えであることはわかっていますが、例としてコードスニペットがなければ、状況に応じてより具体的な答えを推奨することは困難です。

BaseClass の配列ではなく、 BaseClass * の配列を使用します。 (または、スマートポインターライブラリを選択して、メモリを管理します。)

もっと情報が必要だと思います。なぜダウンキャストしているのですか?各派生クラスに対して同じことをしていますか?その場合は、インターフェイスを使用するか、コードを基本クラス(またはその両方)に配置する必要があります。

たとえば、形状の配列(ベース)があり、その面積を計算したい場合、次のようにできます。

    interface IShape
    {
       double GetArea();
    }

    class Square : IShape
    {
       double GetArea()
       {
          return sideLengh*sideLength;
       }
       ...
    }

    class FunnyShape : IShape
    {
       //do funny stuff
       ...
    } 

    ...

void calcTotalArea(List<IShape> shapes)
{
   double total = 0.0;
   foreach (IShape s in shapes)
   {
      total += s.GetArea();
   }
   return total;
}

あなたが何をしようとしているのかまだわかりません。一般にオブジェクトをダウンキャストすることはできませんが、オブジェクトへのポインターをダウンキャストできます。これを行う通常の方法は、fooのタイプがbankAccount *であるdynamic_cast(foo)のようなものです。これにより、MoneyMarketオブジェクトへのポインターまたはNULLポインターが提供されます。

ただし、通常、オブジェクトに対して操作を行うためのダウンキャストは間違いです。これは通常、仮想継承とポリモーフィズムによってより適切に行われます。これは、オブジェクトをさまざまなタイプに分類する方法です。

利息とは何かを知りたい場合、bankAccountで利息を仮想関数として定義すると、利息を支払わない口座タイプはゼロを返すことができます。 MoneyMarketであるアカウントの種類を知りたい場合は、おそらくダウンキャストが最適です。

申し分なく、申し訳ありませんが、希望する最終結果に関するコードともう少しの情報を投稿できたかもしれません。

ここに私の例があります:

class bankAccount
{
   int money;
   bankAccount() {};

   int MoneyInAccount() {return money;};
}

class savingsAccount : public bankAccount
{
   int SavingsInterest();
}

class MoneyMarket : public bankAccount
{
    int CalculateAllPastReturns();
}

それから私のプログラムでは、bankAccount [40]の配列がありました(ポインターではありませんでした)。 BankAccountをSavingsAccount、MoneyMarket、CheckingAccountなどにキャストできることを望んでいました。それぞれに固有のクラスがあります。そこから、プログラムはさまざまな銀行口座とその固有の情報のコレクションを持つことができます。

もちろん、私はそのようにキャストしようとするのは悪いことを知っていますが、どうしたらいいかわかりませんでした。

私は、ポインタなどで露骨に何かを見逃していたことを望んでいました。とにかく、それがもう少し具体的だったことを願っています。助けてくれてありがとう!

bankAccount [40]オブジェクトの配列がある場合、派生クラスを使用することはありません。

instanceof演算子があり、すべて非常に自然であるため、派生クラスへのキャストは非常にJavaっぽいです。 C ++では、reinterpret_cast()でエミュレートできますが、スタイルが悪いと見なされます。

そもそも何をしようとしているのですか?すべてを1つのコンテナーに入れますか?

ここでの問題は、SavingsAccountまたはMoneyMarketオブジェクトではなく、実際のBankAccountオブジェクトを配列に格納していることです。これらの場合、上方へのキャストは機能しません。もちろん、reinterpret_castを使用することもできますが、壊れる可能性があります。 (savingsAccountに追加のメンバー変数(金利)があると想像してください。これにより、基本クラスよりも数バイト大きくなります)

すべきことは、BankAccountオブジェクトへのポインターを保存することです。これらは、任意の派生クラスになります。savingsAccountを割り当て、それへのポインターを配列に入れます。それはうまく機能し、コンパイラはオブジェクト型をうまく追跡し、これらの派生型の1つに必要なすべてのメモリを割り当てました。

今、あなたの貯蓄口座またはマネーマーケット口座を正しく構築し、配列への参照とともにどこかに保存したら、dynamic_castを使用して配列に保持されているポインターを実際の型に変換できます-変換しようとする場合間違ったタイプのオブジェクトへのこれらのポインターのうち、例外が発生します。 (明らかに、savingsAccountオブジェクトを保存して、moneyMarketオブジェクトであるかのようにアクセスしたくない!)、タイプが正しい場合にのみオブジェクトへの有効なポインターを取得します。これを機能させるにはRTTIをオンにする必要があります(つまり、コンパイラは各オブジェクトのタイプを追跡します)。

古き良きCスタイルのユニオンを使用する以外に、目的を達成する別の方法は、アクセスするすべてのメソッドを基本クラスに配置し、保存した基本ポインターで直接呼び出すことです。配列。

すべてを入力した後-litbはほぼ同じことを言っていますが、私よりもはるかに優れています-目盛りを付けてください!

継承チェーンをキャストアップ/ダウンしようとするのは、一般的に間違っています。これを行っている場合、継承とポリモーフィズムを正しく使用していない可能性があります。アップキャストまたはダウンキャストする必要があることに気付いた場合、クラスデザインに欠陥がある可能性があります。

他の回答で述べたように、この種のことを実行する方法は、必要なすべてのメソッドを基本クラスで定義することです。派生クラスがメソッドをサポートしていない場合は、ノーオペレーションにするか、エラーを返すようにします。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top