Thread: destructor & constructor question

  1. #16
    pronounced 'fib' FillYourBrain's Avatar
    Join Date
    Aug 2002
    Posts
    2,297
    its kinda like this:

    constructors can pretty much only be called when you are directly creating the bottom derived class. So the compiler knows all of the constructors all the way up the chain.

    destructors however are called on deleting a pointer. This could be a pointer to a base class where the compiler may have no knowledge of the derived class. so delete baseptr; MUST have a virtual destructor to call if its going to be able call it.
    "You are stupid! You are stupid! Oh, and don't forget, you are STUPID!" - Dexter

  2. #17
    pronounced 'fib' FillYourBrain's Avatar
    Join Date
    Aug 2002
    Posts
    2,297
    just listen to fordy. I think I'm forgetting how to explain things in my old age.
    "You are stupid! You are stupid! Oh, and don't forget, you are STUPID!" - Dexter

  3. #18
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    I've read several C++ books including Bjarne's and none of them ever seem to explain the mechanics behind important little details like this. Most of the examples are either code snippets that are so ignorant they don't apply or are so simple they cannot be used as is.

    I will def. look into those books. Sorry, guess I've managed to derail yet another thread.


    So then w/o virtual destructors it is a good possibility that you are only destroying one object when multiple ones exist thus causing memory leaks.


  4. #19
    Toaster Zach L.'s Avatar
    Join Date
    Aug 2001
    Posts
    2,686
    Its more like destroying only part of an object. For example:

    Code:
    struct base
    {
      base();
      ~base(); // Non-virtual.
    };
    
    struct derived : public base
    {
      derived();
      ~derived();
    };
    
    void foo( )
    {
      base* ptr = new derived;
      delete ptr; // ~base( ) called, but not ~derived( )
    }
    The word rap as it applies to music is the result of a peculiar phonological rule which has stripped the word of its initial voiceless velar stop.

  5. #20
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    BTW Fordy, thanks a million for your help.





  6. #21
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Personally, I think C++ would have been better off with an implicit "virtual" for all destructors; it would be slightly less efficient but it would prevent an easy mistake to make.

    Bubba, the mechanics of a virtual destructor are like any other virtual function.

    The actual way that your compiler makes a "virtual" function is that, in the class, it creates the equivalent of a hidden data member, which has the address of the function. So, if you have:

    virtual int A();

    then the compiler creates a hidden data variable that holds the address of where A() lies in memory. For a non-virtual function, the address is fixed.

    So assume you have:

    Code:
    class B{
    public:
      /*virtual*/ ~B();
    };
    
    class D: public B{
    public:
      ~D();
    };
    
    B* obj = new D();
    delete B;
    Delete gets a pointer to an object of type B. It looks at the destructor and finds that it is not virtual, so it executes B:~B() and nothing else. You end up with a partially destroyed object; anything allocated by D is memory leaked and cannot be recovered.

    Now, if B's destructor was virtual, the following happens:

    Delete gets a pointer to an object of type B. It looks at the destructor and finds that it IS virtual. It then uses the hidden function pointer to call the function. But because the object was created of type D, the function pointer (which is set by the constructor of D as one of the many behind-the-scenes things it does) points at D::~D(). So D::~D() is called. When D::~D() finishes, the last thing it does before it exits (again, automatically generated by the compiler) is call its base class destructor B::~B().

    If you ever plan to use a base pointer to delete a derived object, you need a virtual destructor. Note that if you use a derived pointer to the derived object, the destructor will always be correctly called, regardless of virtual.

    In a way, this is just like any polymorphic function -- if you want to execute the child's destructor with a base pointer, you need a virtual function.

  7. #22
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Ok, that's why I've never thought about this. I don't think I've ever used a base pointer to delete a derived object. Coding

    Code:
    B* obj=new D();
    is not something I normally do.

    But since it is possible the designers of C++ should have made all destructors implicitly virtual since delete knows absolutely nothing about the type of object that it is deleting. Also this would not effect those situations where virtual was not needed but it would correct the problem that occurs when it is.

    Seems like an oversight to me. But like I said - personally I try to avoid creating objects in this way. My normal method is this:

    Code:
    B* obj=new B();
    D* obj=new D();
    Why would you want to access a derived class's constructor from a base pointer?? Why would you use a base pointer to create a derived object?

    Code:
    B* obj=new D();
    Seems unecessarily complex and 'jiggy' to me.

    Code:
    class Object
    {
      public:
         Object(void);
         virtual ~Object(void);
    };
    
    class Square, public Object
    {
      public:
        Square(void);
        ~Square(void);     
    };
    
    Object *obj=new Square();
    If you wanted a Square object, why not just do:

    Code:
    Square *obj=new Square();
    What are the benefits of:

    Code:
    Object *obj=new Square()
    ??



  8. #23
    Toaster Zach L.'s Avatar
    Join Date
    Aug 2001
    Posts
    2,686
    The design philosophy behind no implicit destructors probably comes from C++'s way of giving you "what you ask for, but no more". It saves a little bit of space (no virtual table if no other virtual functions are present) - I agree it makes no sense really, but anyways...

    Its usefull to have a pointer (or reference) to a base class when you don't really care what specialized type it is, you just care about accessing base class functionality. For example, if you had Square, Circle, Triangle, etc all derived from Shape, and you wanted to draw these, you could write:
    Code:
    void drawShape(Shape* s)
    {
      s->draw( );
      s->setColor(red);
    }
    This prevents unnecessary duplication of code (overloaded functions which all do the same thing).
    The word rap as it applies to music is the result of a peculiar phonological rule which has stripped the word of its initial voiceless velar stop.

  9. #24
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Gotcha. Thanks. I have done that before. Sometimes I need to put some meaning to the class examples before I 'get it'. Class B derived from A, etc.,etc. is a little hard to sift through because it's too abstract.

    Alphabet soup.

  10. #25
    Registered User
    Join Date
    Oct 2002
    Posts
    291
    Bubba : polymorphism is the key concept

    A while ago I made a paint look-a-like program. Could draw circles and box etc. This is off the top of my head, I then made these classes :

    Code:
    struct shape
    {
      virtual void draw() const {cout << "draw a shape.." << endl; }
    };
    
    struct circle : public shape
    {
      void draw() const { cout << "draw a circle.." << endl;}
    };
    
    struct box : public shape
    {
      void draw() const {cout << "draw a box.." << endl; }
    };
    On the right hand side of the program I could select what shape to draw, on button for circle, one for box etc. I made a pointer to keep track of what shape was select :

    Code:
      shape* current;
    If a user clicks on 'circle'
    Code:
      current = new circle;
    or for box
    Code:
      current = new box;
    If the user then wants to draw either a circle or a box on the screen I could just do,
    Code:
      current->draw();
    The program will then call either box::draw() or circle::draw() depending on what class the current pointer is pointing to.

    This is my 2 cents worth. Hope I didnt say anything wrong here and hope it helps.

  11. #26
    Registered User Ace Bowers's Avatar
    Join Date
    Jul 2003
    Posts
    23
    So let me get this strait, sorry if this is a noob question, unless a destructor is virtual it will only destroy the class that it's in and is blind, so to speak, to anything within the class. Thanks.
    "I have read about a hill in a book, so the hill must exist." -a Christian quote
    "I have not seen this hill, so it must not exist." -an Athiest quote
    "Well, I've heard of this hill, so lets bomb it." -a George Bush quote

  12. #27
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Umm...yeah I understand that.

    By the way when/where the heck did I start using commas for classes?


    class A:public B, public C {}

    or

    class A:public B {}

    Must have got the comma in the wrong place. Sorry guys.




    Is there a way to disable smilies only for certain sections of our posts?

  13. #28
    pronounced 'fib' FillYourBrain's Avatar
    Join Date
    Aug 2002
    Posts
    2,297
    Why would you want to access a derived class's constructor from a base pointer?? Why would you use a base pointer to create a derived object?
    there is a very good reason for it. I have needed it many times actually. Suppose you have these:

    class Base

    class D1 : public Base

    class D2 : public Base

    std::vector<Base *> list;


    Now throughout your program you add different objects to the list. You don't know what they are but you know you have to delete them at the end.

    Code:
    for(int i=0; i < list.size(); i++)
        delete list[i];
    you see?
    "You are stupid! You are stupid! Oh, and don't forget, you are STUPID!" - Dexter

  14. #29
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Code:
    void drawShape(Shape* s)
    {
      s->draw( );
      s->setColor(red);
    }
    Herein lies my problem. Do you have to declare Square as Shape *obj=new Square in order to do what this drawShape() function does? That is - draws a shape without knowing the type of shape it is drawing.

    See I was thinking from the derived up to the base instead of from the base down to the derived. In your function you are going from base to derived.

    Code:
    class Shape
    {
      public:
        Shape(void) {};
        virtual ~Shape(void) {};
        virtual void Draw(void)=0;
    };
    
    class Square:public Shape
    {
      public:
        Square(void) {};
        ~Square(void) {};
        void Draw(void);
    };
    
    void drawShape(Shape *s);
    
    int main(void)
    {
      Shape *obj1=new Square;
      Square *obj2=new Square;
      drawShape(obj1);
      drawShape(obj2);
    
      delete obj2;
      delete obj1;
      return(0);
    }
    
    void drawShape(Shape* s)
    {
      s->draw( );
      s->setColor(red);
    }
    If both of these work, then destructors should be virtual simply because not having them implicity virtual leaves a lot of room for memory leaks - somewhat transparent to the programmer.

    Granted this code does nothing but it is a point of discussion.

    Sorry for the elongated discussion but this is helping me quite a bit. I normally do not take this approach. Here is how I would usually do it.

    Square->Draw();
    Circle->Draw();

    Which would translate to far more function calls in assembly than is necessary. I don't like having non-member functions messing around with classes like your drawShape does. So my draw would be a virtual protected function. From my code you can tell what is being drawn at a glance - but I can see how the other might be more useful when the type of object in question is not known.
    Last edited by VirtualAce; 07-22-2003 at 08:55 AM.

  15. #30
    Toaster Zach L.'s Avatar
    Join Date
    Aug 2001
    Posts
    2,686
    Yep, both of those work, and it does make more sense (in that case) to use 'Square* obj = new Square', in which case delete will call the correct destructor anyways if it is called in the same function. Consider this code:

    Code:
    class base { };
    class derived_a : public base { };
    class derived_b : public base { };
    
    class builder
    {
    public:
      builder( int );
      ~builder( );
    protected:
      base* create_ptr( int );
    private:
      base* _data;
    };
    
    builder::builder( int x )
    {
      _data = create_ptr( x );
    }
    
    builder::~builder( )
    {
      delete _data;
    }
    
    base* create_ptr( int x )
    {
      // A bit contrived, I know.
      switch( x )
      {
        case 1:
          derived_a* obj1 = new derived_a;
          return obj1;
        case 2:
          derived_b* obj2 = new derived_b;
          return obj2;
      }
    }
    The instances are declared as expected, but when the pointer gets deleted, the function doing so does not know what type of object its pointer is.
    The word rap as it applies to music is the result of a peculiar phonological rule which has stripped the word of its initial voiceless velar stop.

Popular pages Recent additions subscribe to a feed