virtual destructor

S

Stuart Golodetz

JKop said:
Here's what I'm getting at:


Why would you cast a Dog* to a Mammal*? as in:


Mammal restt = new Dog;
delete restt;

Suppose you've got a list of mammals, and you want them all to (say) jump up
and down and shout "Wahey!" (Clearly all mammals do this on a regular
basis...) You don't want to store separate lists of cats and dogs, you just
want to store mammals. (For a more sensible example, consider storing a list
of controls, - you don't care whether it's a drop-down box, a button, or
whatever, all you're interested in is the fact that when you click on it it
does something.) Consider the following (rather odd :)) bit of code:

#include <iostream>
#include <list>

class Mammal
{
public:
virtual void JumpUpAndDownAndShoutWahey() = 0;

virtual ~Mammal()
{
std::cout << "Bye!\n";
}
};

class Dog : public Mammal
{
public:
virtual void JumpUpAndDownAndShoutWahey()
{
std::cout << "I am a dog and I say 'Wahey!'\n";
}

virtual ~Dog()
{
std::cout << "Woof!\n";
}
};

class Cat : public Mammal
{
public:
virtual void JumpUpAndDownAndShoutWahey()
{
std::cout << "I am a cat and I also say 'Wahey!'\n";
}

virtual ~Cat()
{
std::cout << "Miaow!\n";
}
};

int main()
{
std::list<Mammal*> mammalList;
mammalList.push_back(new Dog);
mammalList.push_back(new Cat);
for(std::list<Mammal*>::iterator it=mammalList.begin(),
iend=mammalList.end(); it!=iend; ++it)
{
(*it)->JumpUpAndDownAndShoutWahey();
delete *it; // deleting an instance of a derived class through a
base class pointer - virtual destructor required in the base class or
behaviour is undefined
}
mammalList.clear();
return 0;
}
 
K

Karl Heinz Buchegger

JKop said:
Here's what I'm getting at:

Why would you cast a Dog* to a Mammal*? as in:

Mammal restt = new Dog;
delete restt;

as opposed to:

Dog restt = new Dog;
delete restt;

Because the rest of the program (what you didn't show) might look like this:
(untested code, just to show the idea)

class Mammal
{
public:
virtual ~Mammal() {}
virtual std::string MakeNoise() { return "Error: No noise possible"; }
};

class Dog : public Mammal
{
public:
virtual std::string MakeNoise() { return "Wuff, Wuff"; }
};

class Cat : public Mammal
{
public:
virtual std::string MaekNoise() { return "Miau, Miau"; }
};

int main()
{
Mammal* pMammal = NULL;
int type;

cout << "Which mammal should I create (1 = Dog, 2 = cat) ";
cin >> type;

if( type == 1 )
pMammal = new Dog;
else if( type == 2 )
pMammal = new Cat;

if( pMammal )
cout << "The selected mammal makes " << pMammal->MakeNoise();

delete pMammal;
}
 
J

Jeff Schwab

JKop said:
For the next version of C++ I suggest:

class Mammal
{
virtual_ifderived ~Mammal(void);
};

What would that do? How would it differ from

virtual ~Mammal ( );

?
 
J

jeffc

Jeff Schwab said:
That's a good rule of thumb.


Yes, but I can't think of a case when it's a good idea.

Sometimes people use it as a "trick" to get around the awkward definition of
an abstract class in C++. What if you want an abstract class, but there
isn't any particular function that needs to be pure virtual? Make the
destructor pure virtual.
 
J

jeffc

Jeff Schwab said:
If you provide an implementation for the destructor, in what way is the
destructor "pure virtual?"

There is nothing wrong with providing an implementation for a pure virtual
function. The subclass version can simply call the base class version.
 
J

jeffc

Jeff Schwab said:
Denis said:
10.3.8 says:
"A virtual function [...] shall be defined, or declared pure [...] or both;
[...]"

So yes, you can provide a definition for a pure virtual function.

Thanks, Denis. I've never seen a pure virtual function with a
definition before now. Live and learn!

I think it makes perfect sense, but many people have never considered it.
It is the designer's way of saying "you must think about what you want to do
with this function. You can use my implementation as part or all of your
implementation though, if you wish."
 
J

jeffc

JKop said:
So... when a dog dies, it barks, and then exhales.

How sad.
Long story short, I don't see why there's a need to give Mammal or Dog a
virtual destructor. Could someone please enlighten me?

You just showed why - so when a dog dies, it will also exhale. This is
basic polymorphism - when you have an object such as
Mammal* myDog = new Dog;
 
J

jeffc

JKop said:
Thanks Stephen Waits and Duane Hebert for your replies, although I found the
both of them to be HEAVILY contrived.

Why would one function delete memory allocated by another?

And why would you do the following:

Mammal restt = new Dog;

delete restt;

Looks like child's play to me.

That should be Mammal*. Again, this is basic polymorphism, so you should
read up on that. It's very common in OO code - not at all contrived.
 
J

JKop

jeffc posted:
Sometimes people use it as a "trick" to get around the awkward
definition of an abstract class in C++. What if you want an abstract
class, but there isn't any particular function that needs to be pure
virtual? Make the destructor pure virtual.


If there's no particular function that needs to be pure virtual...


Isn't that what an abstract class is all about! ie. There's things it can't
do because "what it is" isn't specific enough. For example, ask a vehicle to
accelerate - Does it step on the gas pedal, does it pull back the throttle,
or does it pedal?


Could you please give me a quick example of where you want a class to be
abstract... YET... there's no particular function that needs to be pure
virtual. An uncontrived example please.


This interests me!


-JKop
 
J

JKop

Jeff Schwab posted:
What would that do? How would it differ from

virtual ~Mammal ( );

?

The function would *not* be virtual... UNLESS... elsewhere in the program
you have something like:


class Dog : public Mammal


At which point the the compiler makes it virtual. That way there's no
overhead if the class *isn't* derived from... YET... if it is, you have the
functionality of a virtual destructor.

I meant it more as conversation than anything else, ie. I haven't pondered
over the possible pitt-falls.


-JKop
 
J

JKop

jeffc posted:
You just showed why - so when a dog dies, it will also exhale. This is
basic polymorphism - when you have an object such as
Mammal* myDog = new Dog;


My point is that if you give life to a dog, you should kill a dog, ie.:


Dog* dog = new Dog;

delete dog;


You don't give life to a dog and then kill a mammal, ie:


Mammal* dog = new Dog;

delete dog;




Anyway, I've been given allot of examples where a destructor is virtual,
most of them contrived, although a few of them *do* make sense, eg. Linked
lists. So it looks like a judgement call - that's why we have a brain.



-JKop
 
S

Stephen Waits

JKop said:
My point is that if you give life to a dog, you should kill a dog, ie.:

And you just got killed. Welcome to my killfile.

You've crossed the line and are now trolling.. this isn't obscure or
contrived, it's the language definition, it was designed to work this
way, stop fighting it, move on, and actually try to learn something.
Meanwhile, consider checking that huge ass ego at the door - but only if
you really are serious about learning.

--Steve

[very sorry I wasted an ounce of effort trying to help you]
 
J

JKop

Stephen Waits posted:
JKop said:
My point is that if you give life to a dog, you should kill a dog, ie.:

And you just got killed. Welcome to my killfile.

You've crossed the line and are now trolling.. this isn't obscure or
contrived, it's the language definition, it was designed to work this
way, stop fighting it, move on, and actually try to learn something.
Meanwhile, consider checking that huge ass ego at the door - but only if
you really are serious about learning.

--Steve

[very sorry I wasted an ounce of effort trying to help you]


The epitome of pretention.


-JKop
 
J

Jeff Schwab

JKop said:
Jeff Schwab posted:




The function would *not* be virtual... UNLESS... elsewhere in the program
you have something like:


class Dog : public Mammal


At which point the the compiler makes it virtual.

Suppose a Mammal is derived from in one translation unit, but not in
another. Should a Mammal have a different layout in either of the two
resulting object files?
That way there's no
overhead if the class *isn't* derived from... YET... if it is, you have the
functionality of a virtual destructor.

Here's my opinion: If you don't intend to let the class be derived
from, don't include virtual methods. If you do intend to let it be
derived from, be willing to pay the overhead.
 
L

Luther Baker

JKop said:
jeffc posted: ....

My point is that if you give life to a dog, you should kill a dog, ie.:


Dog* dog = new Dog;

delete dog;

Hi JKob,

Ok, try this angle ...

Given your argument above, what is the purpose of having virtual
methods at all?

Your Dog* will always call Dog methods - virtual or not.

Give me an example when *you* would declare and use a virtual method.
Forget destructors for the time being ... just any virtual method.

-Luther
 
J

JKop

Luther Baker posted:
Hi JKob,

Ok, try this angle ...

Given your argument above, what is the purpose of having virtual
methods at all?

Your Dog* will always call Dog methods - virtual or not.

Give me an example when *you* would declare and use a virtual method.
Forget destructors for the time being ... just any virtual method.

-Luther

Interesting to see where this goes :) ...


class SumOfMoney
{
protected:

mutable char string_buffer[30];

public:

long double figure;

virtual const char* GenerateString(void) const
{
//Place the universal currency symbol, it's like an "o" with
//an "x" through it, and then the decimal number

return string_buffer;
}
};


class EuroSumOfMoney : public SumOfMoney
{
virtual const char* GenerateString(void) const
{
//Place the Euro symbol, then the decimal number

return string_buffer;
}
};



Or... if you're a bank and you think that there's no such thing as "just a
some of money", ie. without specified currency, then:


class SumOfMoney
{
protected:

mutable char string_buffer[30];

public:

long double figure;

virtual const char* GenerateString(void) const = 0;

};


class EuroSumOfMoney : public SumOfMoney
{
virtual const char* GenerateString(void) const
{
//Place the Euro symbol, then the decimal number

return string_buffer;
}
};



-JKop
 
L

Luther Baker

JKop said:
Luther Baker posted:
...
Interesting to see where this goes :) ...


class SumOfMoney
{
protected:

mutable char string_buffer[30];

public:

long double figure;

virtual const char* GenerateString(void) const
{
//Place the universal currency symbol, it's like an "o" with
//an "x" through it, and then the decimal number

return string_buffer;
}
};


class EuroSumOfMoney : public SumOfMoney
{
virtual const char* GenerateString(void) const
{
//Place the Euro symbol, then the decimal number

return string_buffer;
}
};

.....

Ok, :) but you left out the most important part. How are you going to
*use* these classes?

Following your earlier examples, you would do this? right?

EuroSumOfMoney* x = new EuroSumOfMoney;

But, then you don't need the methods to be virtual. x->GenerateString()
will always enact the definition from EuroSumOfMoney. virtual or not.

I'm curious ... let me know why you think they *need* to be virtual, or
at least, what do you think "virtual" is buying you here.

Ah, now in the case of your next example:
class SumOfMoney
{
protected:

mutable char string_buffer[30];

public:

long double figure;

virtual const char* GenerateString(void) const = 0;

};

I'm guessing, maybe you thinking that 'pure virtual' is good here
because it somehow, it forces any derived classes to implement this pure
virtual method (compile time error).

But again, if you had:

EuroSumOfMoney* x = new EuroSumOfMoney;

and called:

x->GenerateString();

and you HAD NOT implemented GenerateString in EuroSumOfMoney ... you
would get a compile time error. Still no need to mark anything virtual.

So same thing, let me know what you think *virtual* is buying you. Give
me an actual usage example.

And please don't give a HEAVILY CONTRIVED example :)

-Luther
 
J

JKop

An ATM machine in Tokyo in Japan. You use a special international ATM card
with it. Each ATM card is associated with one and only one bank account,
which has an international identifying number. If *I* get one of these cards
and go to the machine, I can withdraw Euro from my Irish Euro bank account.
If George Bush goes over, he can withdraw US Dollars from his US Dollar bank
account back in Washington DC. If Tony Blair goes over, he can withdraw
Brittish Pounds from his bank account around the corner from No. 10, all
with their international ATM cards.


Now... I myself did not program the actual nitty-gritty stuff of the ATM
machine - all I programmed was the GUI, the display. When the user inserts
their card, they can display their balance. And what is "balance"? It's a
sum of money. It's a number, very nice, you can add, subtract, whatever. But
not all currencies are displayed in the same way (Or numbers for that
matter! 10 == 0xA ). The US Dollar will have $ before it and the Euro will
have € before it. The Iraqi Dinar will be displayed in Arabic digits, and
the currency symbol may be placed at the beginning or end. Chinese currency
will be printed vertically.
But what do *I* care?! I'm just writing the bleeding GUI! So here's my
function:


void DisplayBalance(const SumOfMoney& figure)
{
cout << figure.GenerateString();
}



This is fun! :)


-JKop
 
L

Luther Baker

JKop wrote:
....
But what do *I* care?! I'm just writing the bleeding GUI! So here's my
function:


void DisplayBalance(const SumOfMoney& figure)
{
cout << figure.GenerateString();
}

Ah, so you do realize what is implicitly happening here ... following
your method of doing this ...

EuroSumOfMoney* money = new EuroSumOfMoney;
DisplayBalance(*money);

and if we took a deeper look at "DisplayBalance" we would find that what
the software does is something like this (just note that a derived clas
instance object is being assigned to a base class reference)

void DisplayBalance (...see "figure" below...)
{
// implicitly
const SumOfMoney& figure = *money;

// explicitly
std::cout << figure.GenerateString();
}

So yes, good example of a virtual call. So how would you handle this?
You're in America at an American ATM - so you're going to get American
dollars, but you're British and you have a British bank account.


Currency* GetCurrency(const SumOfMoney& figure)
{
figure.GenerateCurrency();
}


where:


class Currency
{
public:

virtual const char* ConvertToAmericanDollar() const = 0;
virtual const char* ConvertToBritishPound() const = 0;
virtual const char* ConvertToChineseZen() const = 0;
virtual const char* ConvertToBarneyRuble() const = 0;

virtual ~Currency() { }

};

class AmericanDollar : public Currency
{
public:

AmericanDollar(const char* amount)
: amount_(new char[strlen(amount)+1])
{
size_t len = strlen(amount);
strncpy(amount_, amount, len)[len] = '\0';
}

...

virtual double ConvertToAmericanDollar() const
{ return amount_; }

...

virtual ~AmericanDollar()
{ delete [] amount_ }

private:

const char* amount_;

}

....

So your ATM application gets back a Currency* object of which it can
convert to whatever it wants. In fact, maybe mr bank manager inside the
bank can dynamically convert it to whatever he likes.

Now, you aren't in control of the Currency class. Someone else wrote
that. All your application knows is how to "handle" a Currency object.
The American ATM converts results to American Dollars while the British
ATM converts it to pounds - and we could add new currencies by creating
new Currency classes and plugging into your software ... maybe a good
example of the open/closed principle (Meyer).

So, back to the lesson :) What would happen to the "const char* amount_"
that AmericanDollar allocates in its constructor ... if Currency did
*not* have a virtual destructor?

What would happen in your application - if, after getting back a
Currency* and converting the result to AmericanDollars - you deleted the
dynamically allocated Currency object?

Thats not contrived .. is it? Maybe its a bit complicated .. but I don't
think contrived.

I do apologize for any typos above. This code is pseudocode - obviously
not tested or complete.

-Luther
 
R

Risto Lankinen

jeffc said:
Sometimes people use it as a "trick" to get around the awkward definition of
an abstract class in C++. What if you want an abstract class, but there
isn't any particular function that needs to be pure virtual? Make the
destructor pure virtual.

The choice of which method to make pure virtual affects
the derived classes, too: Your choice will force the users
to always override that particular function. If none of the
methods easily lends themselves to be forcedly overridden,
then the base class has no business being a pure virtual
anyway. Stuffing the pure specifier to the destructor just for
the sake of having it somewhere is quite thoughtless when
there is no real necessity to mandate separate clean-up in
individual subclasses.

- Risto -
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top