Reputation: 269
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
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
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
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
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
Reputation: 110648
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*
.
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
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
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