Virtual Destructor

Discussion in 'C++' started by Stub, Nov 6, 2003.

  1. Stub

    Stub Guest

    Please answer my questions below - thanks!

    1. Why "Derived constructor" is called but "Derived destructor" not in Case
    1 since object B is new'ed from Derived class?
    2. Why "Derived destructor" is called in Case 2 since only ~base() becomes
    "virtual" and ~Derived() is still non-virtual?
    3. Does Case 3 show that we don't need any virtual destructor to make
    ~Derived() called?
    4. Is "virtual destructor" needed only for Case 2?


    Case 1:
    ========

    class base
    {
    public:
    ~base() { cout << "Base destructor\n"; }
    };


    class Derived : public base
    {
    public:
    Derived() { cout << "Derived constructor\n"; }
    ~Derived() { cout << "Derived destructor\n"; }
    };


    int main(){
    base* B = new Derived;
    delete B;
    return 0;
    }

    OUTPUT:
    Derived constructor
    Base destructor



    Case 2:
    ========

    class base
    {
    public:
    virtual ~base() { cout << "Base destructor\n"; }
    };


    class Derived : public base
    {
    public:
    Derived() { cout << "Derived constructor\n"; }
    ~Derived() { cout << "Derived destructor\n" }
    };


    int main () {

    base* B = new Derived;
    delete B;
    return 0;
    }

    OUTPUT:
    Derived constructor
    Derived destructor
    Base destructor



    Case 3:
    =========

    class base
    {
    public:
    ~base() { cout << "Base destructor\n"; }
    };


    class Derived : public base
    {
    public:
    Derived() { cout << "Derived constructor\n"; }
    ~Derived() { cout << "Derived destructor\n" }
    };


    int main () {

    Derived* B = new Derived;
    delete B;
    return 0;
    }

    OUTPUT:
    Derived constructor
    Derived destructor
    Base destructor
     
    Stub, Nov 6, 2003
    #1
    1. Advertising

  2. "Stub" <> wrote...
    > Please answer my questions below - thanks!
    >
    > 1. Why "Derived constructor" is called but "Derived destructor" not in

    Case
    > 1 since object B is new'ed from Derived class?


    Probably because there is no virtual destructor and you're deleting
    the object through a pointer to class 'base', not 'Derived'. HOWEVER,
    this behaviour is simply a chance occurrence because if destructor is
    not virtual an attempt to delete an object of a derived type through
    a pointer to a base class causes _undefined_behaviour_.

    > 2. Why "Derived destructor" is called in Case 2 since only ~base() becomes
    > "virtual" and ~Derived() is still non-virtual?


    The "since" part of the question is false. If the base class' d-tor
    is virtual, all derived class' d-tors are virtual too.

    > 3. Does Case 3 show that we don't need any virtual destructor to make
    > ~Derived() called?


    I suppose it does. If you delete it through the pointer to Derived,
    there is no need in a virtual destructor.

    > 4. Is "virtual destructor" needed only for Case 2?


    No. Case 1 requires a virtual destructor. Without it the behaviour
    of the Case 1 program is _undefined_.

    >
    >
    > Case 1:
    > ========
    >
    > class base
    > {
    > public:
    > ~base() { cout << "Base destructor\n"; }
    > };
    >
    >
    > class Derived : public base
    > {
    > public:
    > Derived() { cout << "Derived constructor\n"; }
    > ~Derived() { cout << "Derived destructor\n"; }
    > };
    >
    >
    > int main(){
    > base* B = new Derived;
    > delete B;
    > return 0;
    > }
    >
    > OUTPUT:
    > Derived constructor
    > Base destructor
    >
    >
    >
    > Case 2:
    > ========
    >
    > class base
    > {
    > public:
    > virtual ~base() { cout << "Base destructor\n"; }
    > };
    >
    >
    > class Derived : public base
    > {
    > public:
    > Derived() { cout << "Derived constructor\n"; }
    > ~Derived() { cout << "Derived destructor\n" }
    > };
    >
    >
    > int main () {
    >
    > base* B = new Derived;
    > delete B;
    > return 0;
    > }
    >
    > OUTPUT:
    > Derived constructor
    > Derived destructor
    > Base destructor
    >
    >
    >
    > Case 3:
    > =========
    >
    > class base
    > {
    > public:
    > ~base() { cout << "Base destructor\n"; }
    > };
    >
    >
    > class Derived : public base
    > {
    > public:
    > Derived() { cout << "Derived constructor\n"; }
    > ~Derived() { cout << "Derived destructor\n" }
    > };
    >
    >
    > int main () {
    >
    > Derived* B = new Derived;
    > delete B;
    > return 0;
    > }
    >
    > OUTPUT:
    > Derived constructor
    > Derived destructor
    > Base destructor
    >
    >
    >
    >
     
    Victor Bazarov, Nov 6, 2003
    #2
    1. Advertising

  3. "Stub" <> wrote in message
    news:Yoxqb.207675$...
    > Please answer my questions below - thanks!


    I would, but this does look suspiciously like homework to me. We're all more
    than happy to help with that if there's some evidence of an effort having
    been made, but no-one here is going to do your homework for you. Besides,
    I've got more than enough of my own... :) FWIW, I'll explain the underlying
    idea to you, and if you can figure out the answers to the questions from
    that, then so much the better:

    Given a base class B and a class D (publicly) derived from B, the following
    scenario exhibits undefined behaviour iff B's destructor is non-virtual:

    B *p = new D;
    delete p;

    (Interesting aside - The following always exhibits undefined behaviour,
    whether or not B has a virtual destructor:
    B *p = new D[5];
    delete [] p;
    )

    This is essentially because the compiler merely invokes the destructor
    associated with the static type of p (i.e. B's destructor) as it doesn't
    realise it's supposed to invoke D's destructor. Much as:

    class B
    {
    public:
    void f() { std::cout << "B"; }
    };

    class D : public B
    {
    public:
    void f() { std::cout << "D"; }
    };

    B *p = new D;
    p->f();
    ....

    will output B, since f is non-virtual. However, if we did:

    D *q = new D;
    q->f();
    ....

    then it would output D, since q is a D *.

    Hope that helped a bit, I'll let you figure out the rest for yourself.

    Cheers,

    Stuart.

    > 1. Why "Derived constructor" is called but "Derived destructor" not in

    Case
    > 1 since object B is new'ed from Derived class?
    > 2. Why "Derived destructor" is called in Case 2 since only ~base() becomes
    > "virtual" and ~Derived() is still non-virtual?
    > 3. Does Case 3 show that we don't need any virtual destructor to make
    > ~Derived() called?
    > 4. Is "virtual destructor" needed only for Case 2?
    >
    >
    > Case 1:
    > ========
    >
    > class base
    > {
    > public:
    > ~base() { cout << "Base destructor\n"; }
    > };
    >
    >
    > class Derived : public base
    > {
    > public:
    > Derived() { cout << "Derived constructor\n"; }
    > ~Derived() { cout << "Derived destructor\n"; }
    > };
    >
    >
    > int main(){
    > base* B = new Derived;
    > delete B;
    > return 0;
    > }
    >
    > OUTPUT:
    > Derived constructor
    > Base destructor
    >
    >
    >
    > Case 2:
    > ========
    >
    > class base
    > {
    > public:
    > virtual ~base() { cout << "Base destructor\n"; }
    > };
    >
    >
    > class Derived : public base
    > {
    > public:
    > Derived() { cout << "Derived constructor\n"; }
    > ~Derived() { cout << "Derived destructor\n" }
    > };
    >
    >
    > int main () {
    >
    > base* B = new Derived;
    > delete B;
    > return 0;
    > }
    >
    > OUTPUT:
    > Derived constructor
    > Derived destructor
    > Base destructor
    >
    >
    >
    > Case 3:
    > =========
    >
    > class base
    > {
    > public:
    > ~base() { cout << "Base destructor\n"; }
    > };
    >
    >
    > class Derived : public base
    > {
    > public:
    > Derived() { cout << "Derived constructor\n"; }
    > ~Derived() { cout << "Derived destructor\n" }
    > };
    >
    >
    > int main () {
    >
    > Derived* B = new Derived;
    > delete B;
    > return 0;
    > }
    >
    > OUTPUT:
    > Derived constructor
    > Derived destructor
    > Base destructor
    >
    >
    >
    >
     
    Stuart Golodetz, Nov 6, 2003
    #3
  4. Stub

    Stub Guest

    Victor Bazarov <> wrote in message
    news:2Bxqb.131338$Tr4.337402@attbi_s03...
    > "Stub" <> wrote...
    > > Please answer my questions below - thanks!
    > >
    > > 1. Why "Derived constructor" is called but "Derived destructor" not in

    > Case
    > > 1 since object B is new'ed from Derived class?

    >
    > Probably because there is no virtual destructor and you're deleting
    > the object through a pointer to class 'base', not 'Derived'. HOWEVER,
    > this behaviour is simply a chance occurrence because if destructor is
    > not virtual an attempt to delete an object of a derived type through
    > a pointer to a base class causes _undefined_behaviour_.
    >
    > > 2. Why "Derived destructor" is called in Case 2 since only ~base()

    becomes
    > > "virtual" and ~Derived() is still non-virtual?

    >
    > The "since" part of the question is false. If the base class' d-tor
    > is virtual, all derived class' d-tors are virtual too.
    >

    This is the part which confused me...

    First, is Case 2 the right way to implement virtual destructor so that when
    deleting an object of Derived type via its base class pointer, the
    destructor in Derived will be called for sure?

    Second, the virtual d-tor, "~base()", in base has a different function name
    compared to the virtual d-tor in Derived, "~Derived()." How could it make
    "~Derived()" automatically virtual here? Is there any overriding here given
    their names different? Or is this just designed by C++ Standard. See, I am
    just trying to understand the concept here.

    Thanks for your help!
     
    Stub, Nov 6, 2003
    #4
  5. "Stub" <> wrote...
    > Victor Bazarov <> wrote in message
    > news:2Bxqb.131338$Tr4.337402@attbi_s03...
    > > "Stub" <> wrote...
    > > > Please answer my questions below - thanks!
    > > >
    > > > 1. Why "Derived constructor" is called but "Derived destructor" not in

    > > Case
    > > > 1 since object B is new'ed from Derived class?

    > >
    > > Probably because there is no virtual destructor and you're deleting
    > > the object through a pointer to class 'base', not 'Derived'. HOWEVER,
    > > this behaviour is simply a chance occurrence because if destructor is
    > > not virtual an attempt to delete an object of a derived type through
    > > a pointer to a base class causes _undefined_behaviour_.
    > >
    > > > 2. Why "Derived destructor" is called in Case 2 since only ~base()

    > becomes
    > > > "virtual" and ~Derived() is still non-virtual?

    > >
    > > The "since" part of the question is false. If the base class' d-tor
    > > is virtual, all derived class' d-tors are virtual too.
    > >

    > This is the part which confused me...
    >
    > First, is Case 2 the right way to implement virtual destructor so that

    when
    > deleting an object of Derived type via its base class pointer, the
    > destructor in Derived will be called for sure?


    Case 2 is fine.

    > Second, the virtual d-tor, "~base()", in base has a different function

    name
    > compared to the virtual d-tor in Derived, "~Derived()."


    It doesn't matter. It's a special function. Names here are not important.

    > How could it make
    > "~Derived()" automatically virtual here?


    What's the difference _how_ it does that? The Standard requires it, so it
    has to figure a way.

    > Is there any overriding here given
    > their names different? Or is this just designed by C++ Standard. See, I

    am
    > just trying to understand the concept here.


    The concept is this: for any class T if any of its base classes have their
    destructor declared "virtual", the T's destructor is virtual, no ifs, ands
    or buts.

    Get a copy of the Standard, it's only $18 (in a PDF form). I strongly
    recommend it.

    Victor
     
    Victor Bazarov, Nov 6, 2003
    #5
  6. Stub

    Dan Cernat Guest

    "Stuart Golodetz" <> wrote in message
    news:boe8nn$hvp$...
    > "Stub" <> wrote in message
    > news:Yoxqb.207675$...


    [snip]

    >
    > (Interesting aside - The following always exhibits undefined behaviour,
    > whether or not B has a virtual destructor:
    > B *p = new D[5];
    > delete [] p;
    > )


    Stuart, you know better!. If the destructor is virtual, the code is OK.

    See the code below and the output at the end

    #include <iostream>

    using namespace std;

    class B
    {
    public:
    virtual ~B()
    {
    cout << "B destructor\n";
    }
    };

    class D : public B
    {
    public:
    ~D()
    {
    cout << "D destructor\n";
    }
    };


    int main(int argc, char* argv[])
    {
    B *p = new D[5];

    delete[] p;

    return 0;
    }


    D destructor
    B destructor
    D destructor
    B destructor
    D destructor
    B destructor
    D destructor
    B destructor
    D destructor
    B destructor

    which is the right thing. (VC++ 6.0)

    /dan
     
    Dan Cernat, Nov 7, 2003
    #6
  7. Stub

    Max M Guest

    Dan Cernat wrote:
    >> (Interesting aside - The following always exhibits undefined behaviour,
    >> whether or not B has a virtual destructor:
    >> B *p = new D[5];
    >> delete [] p;
    >> )

    >
    > Stuart, you know better!. If the destructor is virtual, the code is OK.
    >


    No, it's not.

    > See the code below and the output at the end


    Add a data member to D, and see what happens.

    Max
     
    Max M, Nov 7, 2003
    #7
  8. "Max M" <2000.it> wrote in message
    news:bofj8n$hpg$...
    > Dan Cernat wrote:
    > >> (Interesting aside - The following always exhibits undefined behaviour,
    > >> whether or not B has a virtual destructor:
    > >> B *p = new D[5];
    > >> delete [] p;
    > >> )

    > >
    > > Stuart, you know better!. If the destructor is virtual, the code is OK.
    > >

    >
    > No, it's not.
    >
    > > See the code below and the output at the end

    >
    > Add a data member to D, and see what happens.


    Interesting. Why is that?
    --
    Gary
     
    Gary Labowitz, Nov 7, 2003
    #8
  9. Max M wrote in news:bofj8n$hpg$:

    > Dan Cernat wrote:
    >>> (Interesting aside - The following always exhibits undefined
    >>> behaviour, whether or not B has a virtual destructor:
    >>> B *p = new D[5];
    >>> delete [] p;
    >>> )

    >>
    >> Stuart, you know better!. If the destructor is virtual, the code is
    >> OK.
    >>

    >
    > No, it's not.
    >
    >> See the code below and the output at the end

    >
    > Add a data member to D, and see what happens.
    >


    Just for fun I did. I tried vc7.1 first and it worked just fine,
    gcc 3.2 (MingW) seg' faulted and bcc called the B destructor 5 times
    but no seg' fault (It probably would have if B's dtor had been more
    complex, i.e. derefrenced this pointer in some significant way).

    Clearly vc7.1 stores sizeof info in the poloymorfic type (or in its
    vtable) and does "the right thing" when deleteing a polymorphic array.

    AFAICT vc7.1's behaviour an extension to the standard, I really can't
    imagine why they did it :).

    FWIW here's the code:

    #include <iostream>

    using std::cerr;

    int ctor = 0;

    struct test
    {
    int data[10];

    test()
    {
    for ( int i = 0; i < 10; ++i )
    data = 0x5AA55AA5;

    data[1] = ++ctor;
    }
    ~test()
    {
    cerr << "~test(" << data[1] << ")\n";
    }
    };

    class B
    {
    public:
    virtual ~B()
    {
    poly();
    cerr << "B destructor\n";
    }
    virtual void poly() { cerr << "poly B\n"; }
    };

    class D : public B
    {
    test data_member;
    public:
    ~D()
    {
    poly();
    cerr << "D destructor\n";
    }
    virtual void poly() { cerr << "poly D\n"; }
    };


    int main()
    {
    B *p = new D[5];

    delete[] p;
    }

    Rob.
    --
    http://www.victim-prime.dsl.pipex.com/
     
    Rob Williscroft, Nov 7, 2003
    #9
  10. Stub

    Dan Cernat Guest

    Max M <2000.it> wrote in message news:<bofj8n$hpg$>...
    > Dan Cernat wrote:
    > >> (Interesting aside - The following always exhibits undefined behaviour,
    > >> whether or not B has a virtual destructor:
    > >> B *p = new D[5];
    > >> delete [] p;
    > >> )

    > >
    > > Stuart, you know better!. If the destructor is virtual, the code is OK.
    > >

    >
    > No, it's not.
    >
    > > See the code below and the output at the end

    >
    > Add a data member to D, and see what happens.
    >
    > Max


    I tried your suggestion and although I got the behaviour one would
    expect (destruct of derived objects) I have to agree that it could
    lead to undefined behaviour, especially if the pointer is passed
    around and one tries to access its members (not at the index 0).
    However, the memory manager seems that is doing the right job.

    the new code:

    #include <iostream>

    using namespace std;

    class InDerived
    {
    public:
    ~InDerived() {cout << "Destructor InDerived\n";}
    };

    class InBase
    {
    public:
    ~InBase() {cout << "Destructor InBase\n";}
    };


    class base
    {
    public:
    InBase m_inBase;
    virtual ~base() {cout << "Destructor base\n";}
    };

    class derived : public base
    {
    public:
    InDerived m_inDerived;
    int x;
    ~derived() {cout << "Destructor derived\n";}
    };

    void DeleteArray(base *p)
    {
    delete [] p;
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
    base *p = new derived[5];

    DeleteArray(p);


    return 0;
    }
     
    Dan Cernat, Nov 7, 2003
    #10
  11. Stub

    Ron Natalie Guest

    "Dan Cernat" <> wrote in message news:...

    >
    > I tried your suggestion and although I got the behaviour one would
    > expect (destruct of derived objects) I have to agree that it could
    > lead to undefined behaviour, especially if the pointer is passed
    > around and one tries to access its members (not at the index 0).
    > However, the memory manager seems that is doing the right job.


    Not lead to undefined behavior, it is undefined behavior. Coincidentally
    working is one of those insidious undefined behaviors. Try putting
    some more data members in. It will be sure to break. The issue is not
    just delete, you can ONLY convert the derived pointer of one object to
    a base class. It doesn't work in arrays.

    Image sizeof(base) is 4. sizeof(derived) is 8.
    What is the relationship the base_array[1] with respect to base_array[0].
    What is the relationship of derived_array[1] to derived_array[0].
    Just think about it for a minute.
     
    Ron Natalie, Nov 7, 2003
    #11
  12. Stub

    Dan Cernat Guest

    "Ron Natalie" <> wrote in message
    news:3fabdedc$0$3632$...
    >
    > "Dan Cernat" <> wrote in message

    news:...
    >
    > >
    > > I tried your suggestion and although I got the behaviour one would
    > > expect (destruct of derived objects) I have to agree that it could
    > > lead to undefined behaviour, especially if the pointer is passed
    > > around and one tries to access its members (not at the index 0).
    > > However, the memory manager seems that is doing the right job.

    >
    > Not lead to undefined behavior, it is undefined behavior. Coincidentally
    > working is one of those insidious undefined behaviors. Try putting
    > some more data members in. It will be sure to break. The issue is not
    > just delete, you can ONLY convert the derived pointer of one object to
    > a base class. It doesn't work in arrays.
    >
    > Image sizeof(base) is 4. sizeof(derived) is 8.
    > What is the relationship the base_array[1] with respect to base_array[0].
    > What is the relationship of derived_array[1] to derived_array[0].
    > Just think about it for a minute.
    >

    Yup, this is what I wanted to say in my prevoius message and I totally agree
    with what you say.
    I really should think twice before posting. Point taken.

    Thanks,
    Dan
     
    Dan Cernat, Nov 10, 2003
    #12
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Calvin Lai
    Replies:
    7
    Views:
    574
    Calvin Lai
    Dec 18, 2003
  2. Chunhui Han
    Replies:
    2
    Views:
    531
  3. frs
    Replies:
    20
    Views:
    792
    Alf P. Steinbach
    Sep 21, 2005
  4. arun
    Replies:
    2
    Views:
    577
    benben
    Jun 13, 2006
  5. Jimmy Hartzell
    Replies:
    0
    Views:
    439
    Jimmy Hartzell
    May 19, 2008
Loading...

Share This Page