nat45928
nat45928

Reputation: 269

C++ dynamic casting

I have a main abstract class that I am provided and must create subclasses based on this class (THIS CANNOT BE MODIFIED):

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

So is an example of my child classes:

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; }
};

in my main code I have an dynamic array of different types of Spaceships:

Spaceship ** ships;

cin >> numShips;

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

Then i get input from the user to declare different types of ships in this array like:

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

my question is when I go to delete the array the correct destructor is not called, instead Spaceships is called, will this create a memory leak because the member vars "m_shield, m_armor" are not deleted and left hanging? If so is there a better way to get the type than using a var m_type and calling:

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]);

Question #2 in the Spaceship class i declared: virtual int typeOf() = 0; and commented it out, is there a way i can implement this function in the child classes without declaring in the parent class so i can use it like shown above? when i dont declare it i get the compiler error:

error: 'class Spaceship' has no member named 'typeOf'

I assume this again has something to do with dynamic casing.

any help would be great,

Thanks nat

edit:

to clairify my first question would i have a memory leak if I just did:

delete ships[i];

or should i do:

delete dynamic_cast(ships[i]);

to remove member vars that are only in the derived classes?

Thnaks

Upvotes: 2

Views: 1059

Answers (7)

DummyDDD
DummyDDD

Reputation: 52

Question 1: You may get a memory leak if any of the deriving classes or their members have non-trivial (defined) destructors. Basically, if the deriving classes and their members do nothing at destruction, then you are not going to get a memory leak. You may however get undefined behavior if the delete operator is overloaded for the Spaceship class.

Question 2: No, you cannot call a method on a class that does not have the method (typeOf on a Spaceship).

If the derived classes or their members have non-trivial destructors, you should define a new class that publicly derives from the Spaceship and has a virtual destructor. Classes derived from the new class are properly destroyed.

Alternatively, you can keep an array for each type of spaceship. This would also avoid the need for any virtual methods.

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275220

First, note "(THIS CANNOT BE MODIFIED)" is a bad sign. It means this answer will be a hack.

What you will have to do is build your own manual type system. Build a mapping from typeid (well, I'd use typeid().hash()) to a destroyer functions that call the destructor of the child class in question.

This isn't for the faint of heart, and is a really bad idea. The right answer is "use a virtual destructor".

Similar techniques can be used to create fake-virtual functions, where you maintain your own external and manual "fake-virtual function table" that maps the typeid of the instance to an entry in the table. You can even implement fake inheritance of these fake-virtual functions with the judicious use of CRTP (where you manually build the inheritance tree based off typeid().hash(), then manually search your fake-virtual function maps to check parents in whatever order you want).

At this point, what you are basically doing is implementing your own object system. And this is not something you should do unless you know what you are doing.

Upvotes: 2

Olaf Dietsche
Olaf Dietsche

Reputation: 74018

You must add a virtual destructor to your Spaceship class. Then delete will properly destroy the elements from the array.

You must declare the typeOf() method in Spaceship. Otherwise, the compiler cannot know, that this is a valid member function.

You can avoid typeOf(), if you add the needed functionality as virtual member functions in your base class.

And even if you cannot modify your base class for whatever reasons, you can just do the dynamic_cast and test, if it yields a null pointer

Frigate *p = dynamic_cast<Frigate*>(ships[i]);
if (p != 0) {
    // do something with a frigate
}

Upvotes: 5

Andy Prowl
Andy Prowl

Reputation: 126412

Just declare a virtual destructor in the Spaceship class, and you will be able to polymorphically delete an object through a pointer to the base class:

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

    ...
};

This will allow you to avoid the dynamic_cast<>s and the typeOf() function (if this is what you need it for):

delete ships[i];

Even better, you should consider using smart pointers (e.g. std::shared_ptr<>) and std::vector<> to avoid manual memory management at all:

#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
// ...

Upvotes: 4

Joseph Mansfield
Joseph Mansfield

Reputation: 110648

  1. You should simply provide a virtual destructor:

    virtual ~Spaceship() { }
    

    Then the appropriate destructors of derived classes will be called when deleting them through a Spaceship*.

  2. If you want typeOf to be called through a Spaceship* then it must be a virtual member of that class.

The important thing to understand about both of these questions is what exactly virtual tells the compiler. When you have a Spaceship* pointing at a Dreadnought object, Spaceship is the static type of the object when accessed through that pointer and Dreadnought is the dynamic type. When you call a member function through a Spaceship* and the compiler finds that it is virtual, it then knows to look up the dynamic type of the object to find out which function it should really call.

Upvotes: 4

Qortex
Qortex

Reputation: 7456

This is a classical pitfall in inheritance hierarchies, you absolutely need to have a virtual destructor when you derive from a base class and intend to use polymorphism for deletion.

I also suggest your turn to smart pointers instead of taking care of the allocation and deallocation yourself. It basically does all this stuff for you in a neat and secure way.

As far as question 2 is concerned, no you cannot. If you want to be able to call a function on the base class, the base class must expose it. That's the whole point of polymorphism: you don't care about how the action is actually implemented in whatever derived class you are dealing with, but you expect all the classes to feature the same interface. So you have to define that interface to make it available to the outside world.

Upvotes: 1

Alan
Alan

Reputation: 46813

Question1: Actually you shouldn't have a memory leak.

This is why you always declare destructors as virtual.

Even when you delete a spaceship pointer that points to dreadnought, dreadnoughts destructor will be called.

Upvotes: 1

Related Questions