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并测试,如果它产生一个空指针

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通过 a 被调用,Spaceship*那么它必须是该类的虚拟成员。

关于这两个问题,重要的是要了解virtual编译器的确切信息。当您Spaceship*指向一个Dreadnought对象时,Spaceship是通过该指针访问时对象的静态类型Dreadnought,并且是动态类型。当您通过 a 调用成员函数Spaceship*并且编译器发现它是virtual时,它就会知道查找对象的动态类型以找出它应该真正调用哪个函数。

于 2013-03-08T22:14:11.083 回答
2

首先,请注意“(无法修改)”是一个不好的信号。这意味着这个答案将是一个黑客。

您需要做的是构建自己的手动类型系统。构建从typeid(好吧,我会使用typeid().hash())到调用相关子类的析构函数的销毁函数的映射。

这不适合胆小的人,而且是一个非常糟糕的主意。正确的答案是“使用虚拟析构函数”。

类似的技术可用于创建假虚拟函数,您可以在其中维护自己的外部和手动“假虚拟函数表”,将实例的 typeid 映射到表中的条目。您甚至可以通过明智地使用 CRTP 来实现这些假虚拟函数的假继承(您可以在其中手动构建基于 off 的继承树typeid().hash(),然后手动搜索假虚拟函数映射以按照您想要的任何顺序检查父级)。

在这一点上,你基本上在做的是实现你自己的对象系统。除非您知道自己在做什么,否则这不是您应该做的事情。

于 2013-03-08T22:42:45.917 回答
1

问题1:实际上你不应该有内存泄漏。

这就是为什么你总是将析构函数声明为虚拟的。

即使你删除了一个指向 dreadnought 的飞船指针,dreadnoughts 的析构函数也会被调用。

于 2013-03-08T22:11:42.777 回答
1

这是继承层次结构中的一个典型陷阱,当您从基类派生并打算使用多态性进行删除时,您绝对需要一个虚拟析构函数。

我还建议您转向智能指针,而不是自己处理分配和释放。它基本上以简洁安全的方式为您完成所有这些工作。

就问题2而言,不,你不能。如果您希望能够调用基类上的函数,则基类必须公开它。这就是多态性的全部意义:您不关心在您处理的任何派生类中该操作是如何实际实现的,但您希望所有类都具有相同的接口。因此,您必须定义该接口以使其对外部世界可用。

于 2013-03-08T22:13:50.387 回答
0

问题 1:如果任何派生类或其成员具有非平凡(已定义)析构函数,您可能会发生内存泄漏。基本上,如果派生类及其成员在销毁时什么都不做,那么您就不会发生内存泄漏。但是,如果为 Spaceship 类重载了 delete 运算符,您可能会得到未定义的行为。

问题 2:不,您不能在没有方法的类上调用方法(宇宙飞船上的 typeOf)。

如果派生类或其成员具有重要的析构函数,则应定义一个公开派生自 Spaceship 并具有虚拟析构函数的新类。从新类派生的类被正确销毁。

或者,您可以为每种类型的宇宙飞船保留一个数组。这也将避免对任何虚拟方法的需要。

于 2013-03-09T01:22:44.777 回答