2

提供されたメインの抽象クラスがあり、このクラスに基づいてサブクラスを作成する必要があります (これは変更できません)。

class Spaceship
{
  protected:
    string m_name;      // the name of the ship
    int m_hull;         // the hull strenght
  public:

    // Purpose: Default Constructor
    // Postconditions: name and hull strength set to parameters
    // -- INLINE
    Spaceship(string n, int h)
    {
      m_name = n;
      m_hull = h;
    }

    // Purpose: Tells if a ship is alive.
    // Postconditions: 'true' if a ship's hull strength is above zero,
    //                 'false' otherwize.
    // -- INLINE
    bool isAlive()
    {
      return (m_hull > 0);
    }

    // Purpose: Prints the status of a ship.
    // -- VIRTUAL
    virtual void status() const = 0;

    // Purpose: Changes the status of a ship, when hit by a
    //    weapon 's' with power level 'power'
    // -- VIRTUAL
    virtual void hit(weapon s, int power) = 0;

    string getName() const
    {
      return m_name;
    }

}; //Spaceship

私の子クラスの例は次のとおりです。

class Dreadnought: public Spaceship
{
  int m_shield;
  int m_armor;
  int m_type;
  public:
    Dreadnought( string n, int h, int a, int s ): Spaceship( n, h ),m_shield( s ),m_armor(a),m_type(dreadnought){}
    virtual void status() const
    {
      // implementation not shown to save space
    }
    virtual void hit(weapon s, int power)
    {
      // implementation not shown to save space
    }

    int typeOf(){ return m_type; }
};

私のメインコードには、さまざまなタイプの宇宙船の動的配列があります。

Spaceship ** ships;

cin >> numShips;

// create an array of the ships to test
ships = new Spaceship * [numShips];

次に、ユーザーから入力を取得して、次のように、この配列でさまざまなタイプの船を宣言します。

ships[0] = new Dreadnought( name, hull, armor, shield );

私の質問は、配列を削除しようとすると正しいデストラクタが呼び出されず、代わりに Spaceships が呼び出されることです。これにより、メンバ変数 "m_shield、m_armor" が削除されずにハングしたままになるため、メモリ リークが発生しますか? その場合、var m_type を使用して呼び出すよりも、型を取得するためのより良い方法があります。

if( ships[i]->typeOf() == 0 )
      delete dynamic_cast<Frigate*>(ships[i]);
    else if( ships[i]->typeOf() == 1 )
      delete dynamic_cast<Destroyer*>(ships[i]);
    else if( ships[i]->typeOf() == 2 )
      delete dynamic_cast<Battlecruiser*>(ships[i]);
    else if( ships[i]->typeOf() == 3 )
      delete dynamic_cast<Dreadnought*>(ships[i]);
    else
      delete dynamic_cast<Dropship*>(ships[i]);

私が宣言した Spaceship クラスの質問 #2: virtual int typeOf() = 0; コメントアウトしましたが、親クラスで宣言せずにこの関数を子クラスに実装して、上記のように使用できる方法はありますか? 宣言しないと、コンパイラエラーが発生します:

エラー: 'class Spaceship' には 'typeOf' という名前のメンバーがありません

これも動的ケーシングと関係があると思います。

どんな助けでも素晴らしいでしょう、

ありがとうございます

編集:

私の最初の質問を明確にするために、私がした場合、メモリリークが発生しますか?

船を削除[i];

または私はすべきですか:

dynamic_cast(ships[i]); を削除します。

派生クラスのみにあるメンバー変数を削除するには?

タナクス

4

7 に答える 7

5

クラスに仮想デストラクタを追加する必要がありますSpaceship。次にdelete、配列から要素を適切に破棄します。

typeOf()でメソッドを宣言する必要がありSpaceshipます。そうしないと、コンパイラはこれが有効なメンバー関数であることを認識できません。

typeOf()必要な機能を基本クラスの仮想メンバー関数として追加すると、回避できます。

また、何らかの理由で基本クラスを変更できない場合でもdynamic_cast、null ポインターが生成される場合は、and テストを実行できます。

Frigate *p = dynamic_cast<Frigate*>(ships[i]);
if (p != 0) {
    // do something with a frigate
}
于 2013-03-08T22:12:33.367 に答える
4

virtualクラスでデストラクタを宣言するだけで、基本クラスへのポインタを介してオブジェクトSpaceshipをポリモーフィックにできます。delete

class Spaceship
{
    virtual ~Spaceship() { }
//  ^^^^^^^
//  Allows destroying instances of derived classes by deleting
//  pointers of type Spaceship*.

    ...
};

dynamic_cast<>これにより、 s と関数を回避できますtypeOf()(これが必要な場合):

delete ships[i];

さらに良いことに、スマート ポインター (例: std::shared_ptr<>) の使用を検討し、手動のメモリ管理をまったく回避std::vector<>する必要があります。

#include <memory>
#include <vector>

std::vector<std::shared_ptr<Spaceship>> ships;
ships.push_back(std::make_shared<Dreadnough>(name, hull, armor, shield));
std::string s = ships[0]->getName();

// ...
// And you don't need to delete anything
// ...
于 2013-03-08T22:13:26.733 に答える
4
  1. 仮想デストラクタを提供するだけです。

    virtual ~Spaceship() { }
    

    次に、派生クラスの適切なデストラクタが、 を介してそれらを削除するときに呼び出されますSpaceship*

  2. typeOfを介して呼び出される場合Spaceship*は、そのクラスの仮想メンバーである必要があります。

これらの両方の質問について理解しておくべき重要なことvirtualは、コンパイラーに正確に伝える内容です。オブジェクトをSpaceship*指している場合、そのポインターを介してアクセスしたときのオブジェクトの静的型であり、動的型です。a を介してメンバー関数を呼び出すと、コンパイラはそれがであることを検出すると、オブジェクトの動的な型を調べて、実際に呼び出す必要がある関数を見つけます。DreadnoughtSpaceshipDreadnoughtSpaceship*virtual

于 2013-03-08T22:14:11.083 に答える
2

まず、「(THIS CANNOT BE MODIFIED)」は悪い兆候であることに注意してください。これは、この答えがハックになることを意味します。

あなたがしなければならないことは、独自の手動型システムを構築することです。問題の子クラスのデストラクタを呼び出すデストラクタ関数へのtypeid(まあ、私は使用します)からのマッピングを構築します。typeid().hash()

これは気弱な人向けではなく、本当に悪い考えです。正解は「仮想デストラクタを使う」です。

同様の手法を使用して、偽の仮想関数を作成できます。この場合、インスタンスの typeid をテーブルのエントリにマップする独自の外部および手動の「偽の仮想関数テーブル」を維持します。CRTP を賢明に使用して、これらの偽の仮想関数の偽の継承を実装することもできます (ここでは、 offtypeid().hash()に基づいて継承ツリーを手動で構築し、偽の仮想関数マップを手動で検索して、必要な順序で親を確認します)。

この時点で、基本的に行っていることは、独自のオブジェクト システムを実装することです。そして、これは、自分が何をしているのかを理解していない限り、すべきことではありません。

于 2013-03-08T22:42:45.917 に答える
1

質問 1: 実際には、メモリ リークは発生しないはずです。

これが、デストラクタを常に仮想として宣言する理由です。

ドレッドノートを指す宇宙船ポインタを削除しても、ドレッドノートのデストラクタが呼び出されます。

于 2013-03-08T22:11:42.777 に答える
1

これは継承階層の古典的な落とし穴です。基本クラスから派生し、削除にポリモーフィズムを使用する場合は、仮想デストラクタが絶対に必要です。

また、割り当てと割り当て解除を自分で行うのではなく、スマート ポインターを使用することをお勧めします。基本的に、これらすべてをきちんと安全な方法で行います。

質問 2 に関する限り、いいえ、できません。基本クラスで関数を呼び出せるようにする場合は、基本クラスでそれを公開する必要があります。これがポリモーフィズムの要点です。処理している派生クラスでアクションが実際にどのように実装されているかは気にしませんが、すべてのクラスが同じインターフェイスを備えていることを期待します。したがって、そのインターフェイスを定義して、外部から利用できるようにする必要があります。

于 2013-03-08T22:13:50.387 に答える