Virtual destructor

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • pmizzi

    Virtual destructor

    When i compile my program with the -ansi -Wall -pedantic
    flags,
    i get this warning: `class vechile' has virtual functions
    but non-virtual destructor, and the same with my
    sub-classes. But when i add a virtual destructor like this :

    " virtual ~vechile". I get this error:
    Undefined first referenced
    symbol in file
    vtable for vechile /var/tmp//ccC9yD6Z.o
    ld: fatal: Symbol referencing errors. No output written to
    a.out
    collect2: ld returned 1 exit status


    Can anyone help me?

  • Ian

    #2
    Re: Virtual destructor

    pmizzi wrote:[color=blue]
    > When i compile my program with the -ansi -Wall -pedantic
    > flags,
    > i get this warning: `class vechile' has virtual functions
    > but non-virtual destructor, and the same with my
    > sub-classes. But when i add a virtual destructor like this :
    >
    > " virtual ~vechile". I get this error:[/color]

    You haven't provides a body for the destructor. If it does nothing ,
    then ~vechile() {} will suffice.

    Ian

    Comment

    • E. Robert Tisdale

      #3
      Re: Virtual destructor

      pmizzi wrote:
      [color=blue]
      > When I compile my program with the -ansi -Wall -pedantic flags,
      > I get this warning: `class vechile' has virtual functions
      > but non-virtual destructor,[/color]

      It's a warning. Ignore it.
      [color=blue]
      > and the same with my sub-classes.
      > But, when I add a virtual destructor like this:
      >
      > "virtual ~vechile", I get this error:
      >
      > Undefined first referenced
      > symbol in file
      > vtable for vechile /var/tmp//ccC9yD6Z.o
      > ld: fatal: Symbol referencing errors. No output written to
      > a.out
      > collect2: ld returned 1 exit status
      >
      > Can anyone help me?[/color]

      Don't define a virtual destructor if you don't need one.
      If you really need a virtual destructor,
      you will need virtual constructors as well.
      No function should ever delete an object
      that was allocated by the calling function and,
      in C++, it is poor programming practice
      to require the calling program
      to explicitly delete an object through a pointer
      returned by a function that allocated the object.

      Comment

      • Ali Çehreli

        #4
        Re: Virtual destructor

        "E. Robert Tisdale" <E.Robert.Tisda le@jpl.nasa.gov > wrote in message
        news:di0tg9$lbb $1@nntp1.jpl.na sa.gov...[color=blue]
        > pmizzi wrote:
        >[color=green]
        >> When I compile my program with the -ansi -Wall -pedantic flags,
        >> I get this warning: `class vechile' has virtual functions
        >> but non-virtual destructor,[/color]
        >
        > It's a warning. Ignore it.[/color]

        It is a warning that should not be ignored in almost all cases. A virtualy
        destructor is only rarely unnecessary. Even then, not defining virtual would
        be a timebomb.

        Deleting a polymorphic object through a base class pointer is very common.
        It is undefined behavior when the destructor is non-virtual in that case.
        [color=blue][color=green]
        >> and the same with my sub-classes.
        >> But, when I add a virtual destructor like this:
        >>
        >> "virtual ~vechile", I get this error:
        >>
        >> Undefined first referenced
        >> symbol in file
        >> vtable for vechile /var/tmp//ccC9yD6Z.o
        >> ld: fatal: Symbol referencing errors. No output written to
        >> a.out
        >> collect2: ld returned 1 exit status
        >>
        >> Can anyone help me?[/color]
        >
        > Don't define a virtual destructor if you don't need one.[/color]

        That is very rarely the case.
        [color=blue]
        > If you really need a virtual destructor,
        > you will need virtual constructors as well.[/color]

        Not at all. Using a function to make the decision on what object to
        construct and to return a base object pointer is very common:

        Base * read_object(/* ... */)
        {
        /* ... */
        return some_condition
        ? new Derived1
        : new Derived2;
        }

        /* ... */

        Base * base = read_object(/* ... */);
        [color=blue]
        > No function should ever delete an object
        > that was allocated by the calling function[/color]

        I can think of a linked-list implementation that has a function, which
        removes objects. Surely the objects would not be allocated in that function,
        yet deleted in there.
        [color=blue]
        > and,
        > in C++, it is poor programming practice
        > to require the calling program
        > to explicitly delete an object through a pointer
        > returned by a function that allocated the object.[/color]

        See the read_object function above. How do you propose we delete the
        returned object? Of course approach is to return a suitable smart pointer
        that communicates precisely the transfer of ownership, which in turn means
        that the object will be deleted outside of the allocating function.

        Ali

        Comment

        • E. Robert Tisdale

          #5
          Re: Virtual destructor

          Ali Çehreli wrote:
          [color=blue]
          > E. Robert Tisdale wrote:
          >[color=green]
          >> If you really need a virtual destructor,
          >> you will need virtual constructors as well.[/color]
          >
          > Not at all. Using a function to make the decision on what object to
          > construct and to return a base object pointer is very common:
          >
          > Base* read_object(/* ... */) {
          > /* ... */
          > return some_condition? new Derived1: new Derived2;
          > }
          >
          > /* ... */
          >
          > Base* base = read_object(/* ... */);[/color]

          But it isn't good programming practice in C++.
          Your program already knows about types Derived1 and Derived2
          so you might as well use them in the calling program.

          A virtual constructor is more "extensible ".
          See Bjarne Stroustrup, "The C++ Programming Language: Third Edition",
          Chapter 15: Class Hierarchies, Section 6: Free Store,
          Subsection 2: Virtual constructors, pages 424-5.

          Comment

          • Ali Çehreli

            #6
            Re: Virtual destructor

            "E. Robert Tisdale" <E.Robert.Tisda le@jpl.nasa.gov > wrote in message
            news:di1e0g$v4$ 1@nntp1.jpl.nas a.gov...[color=blue]
            > Ali Çehreli wrote:
            >[color=green]
            >> E. Robert Tisdale wrote:
            >>[color=darkred]
            >>> If you really need a virtual destructor,
            >>> you will need virtual constructors as well.[/color]
            >>
            >> Not at all. Using a function to make the decision on what object to
            >> construct and to return a base object pointer is very common:
            >>
            >> Base* read_object(/* ... */) {
            >> /* ... */
            >> return some_condition? new Derived1: new Derived2;
            >> }
            >>
            >> /* ... */
            >>
            >> Base* base = read_object(/* ... */);[/color]
            >
            > But it isn't good programming practice in C++.[/color]

            Himmm... I don't see what you mean.

            Even Stroustrup refers to such a use in C++ in the pages you quoted. My
            read_object does exactly the same thing as his Ival_maker::dia l does. Maybe
            naming it as "factory method" might
            [color=blue]
            > Your program already knows about types Derived1 and Derived2
            > so you might as well use them in the calling program.[/color]

            My program knows of them, but the translation unit does not know of them. We
            don't always mention the physical structure of code in our posts, but you
            can assume that the declaration is

            Base * read_object(/* ... */);

            and the user is

            Base * base = read_object(/* ... */);

            So, Derived1 and Derived2 are know only in another translation unit, where
            read_object is defined.
            [color=blue]
            > A virtual constructor is more "extensible ".[/color]

            It is not more extensible than a function like read_object; you can extend
            read_object any way we like. Besides, their application are very different:
            read_object returns a brand new object, virtual constructor copies an
            existing object. That's why I use virtual constructor in some of my copy
            constructors.
            [color=blue]
            > See Bjarne Stroustrup, "The C++ Programming Language: Third Edition",
            > Chapter 15: Class Hierarchies, Section 6: Free Store,
            > Subsection 2: Virtual constructors, pages 424-5.[/color]

            Thanks for the refence; I've re-read it. I think he could declare new_expr
            as a static member function. Then it could be used without an existing
            object; esentially using the same principle as a simple function as
            read_object above.

            Ali

            Comment

            • E. Robert Tisdale

              #7
              Re: Virtual destructor

              Ali Çehreli wrote:
              [color=blue]
              > E. Robert Tisdale wrote:
              >[color=green]
              >> A virtual constructor is more "extensible ".[/color]
              >
              > It is not more extensible than a function like read_object;
              > you can extend read_object any way we like.
              > Besides, their application are very different:
              > read_object returns a brand new object,
              > virtual constructor copies an existing object.
              > That's why I use virtual constructor in some of my copy constructors.
              >[color=green]
              >> See Bjarne Stroustrup, "The C++ Programming Language: Third Edition",
              >> Chapter 15: Class Hierarchies, Section 6: Free Store,
              >> Subsection 2: Virtual constructors, pages 424-5.[/color][/color]

              Stoustrup's example shows a virtual "default constructor" (new_expr)
              and a virtual "copy constructor" (clone) corresponding to
              the actual constructors for class Expr.
              [color=blue]
              > Thanks for the refence; I've re-read it.
              > I think he could declare new_expr as a static member function.
              > Then it could be used without an existing object;
              > esentially using the same principle
              > as a simple function as read_object above.[/color]

              That means that the base class would need to know about
              all of the derived types which are, generally, unknown
              when the base class is implemented.

              Comment

              • Dave Rahardja

                #8
                Re: Virtual destructor

                On Wed, 05 Oct 2005 09:03:33 -0700, "E. Robert Tisdale"
                <E.Robert.Tisda le@jpl.nasa.gov > wrote:
                [color=blue]
                >pmizzi wrote:
                >[color=green]
                >> When I compile my program with the -ansi -Wall -pedantic flags,
                >> I get this warning: `class vechile' has virtual functions
                >> but non-virtual destructor,[/color]
                >
                >It's a warning. Ignore it.[/color]

                Famous last words. Warnings should be dealt with, or at least documented,
                because they indicate an error-prone construct.

                Destructors should almost always be declared virtual. Without virtual
                destructors derived classes stand a good chance of not getting destroyed
                correctly.
                [color=blue]
                >If you really need a virtual destructor,
                >you will need virtual constructors as well.[/color]

                Virtual destructors and "virtual constructors" (vis-a-vis Stroustroup) solve
                very different problems. Don't confuse them with each other.
                [color=blue]
                >No function should ever delete an object
                >that was allocated by the calling function and,
                >in C++, it is poor programming practice
                >to require the calling program
                >to explicitly delete an object through a pointer
                >returned by a function that allocated the object.[/color]

                Nonsense. std::auto_ptr uses that idiom.

                There are situations where an Orchestrator may construct an object on the heap
                derived from some base class, then passes a reference to that object on the
                heap to a helper object as a reference to the base class. The helper object
                then "owns" and manages that object, and will destruct it on its own schedule.

                There's nothing wrong with that pattern.

                -dr

                Comment

                • Richard Herring

                  #9
                  Re: Virtual destructor

                  In message <di1e0g$v4$1@nn tp1.jpl.nasa.go v>, E. Robert Tisdale
                  <E.Robert.Tisda le@jpl.nasa.gov > writes[color=blue]
                  >Ali Çehreli wrote:
                  >[color=green]
                  >> E. Robert Tisdale wrote:
                  >>[color=darkred]
                  >>> If you really need a virtual destructor,
                  >>> you will need virtual constructors as well.[/color]
                  >> Not at all. Using a function to make the decision on what object to
                  >>construct and to return a base object pointer is very common:
                  >> Base* read_object(/* ... */) {
                  >> /* ... */
                  >> return some_condition? new Derived1: new Derived2;
                  >> }
                  >> /* ... */
                  >> Base* base = read_object(/* ... */);[/color]
                  >
                  >But it isn't good programming practice in C++.
                  >Your program already knows about types Derived1 and Derived2
                  >so you might as well use them in the calling program.[/color]

                  So virtual functions are a waste of time. You heard it here first,
                  folks!
                  [color=blue]
                  >
                  >A virtual constructor is more "extensible ".
                  >See Bjarne Stroustrup, "The C++ Programming Language: Third Edition",
                  >Chapter 15: Class Hierarchies, Section 6: Free Store,
                  >Subsection 2: Virtual constructors, pages 424-5.[/color]

                  --
                  Richard Herring

                  Comment

                  • Richard Herring

                    #10
                    Re: Virtual destructor

                    In message <di0tg9$lbb$1@n ntp1.jpl.nasa.g ov>, E. Robert Tisdale
                    <E.Robert.Tisda le@jpl.nasa.gov > writes[color=blue]
                    >pmizzi wrote:
                    >[color=green]
                    >> When I compile my program with the -ansi -Wall -pedantic flags,
                    >> I get this warning: `class vechile' has virtual functions
                    >> but non-virtual destructor,[/color]
                    >
                    >It's a warning. Ignore it.
                    >[color=green]
                    >> and the same with my sub-classes.
                    >> But, when I add a virtual destructor like this:
                    >> "virtual ~vechile", I get this error:
                    >>
                    >> Undefined first referenced
                    >> symbol in file
                    >> vtable for vechile /var/tmp//ccC9yD6Z.o
                    >> ld: fatal: Symbol referencing errors. No output written to
                    >> a.out
                    >> collect2: ld returned 1 exit status
                    >> Can anyone help me?[/color]
                    >
                    >Don't define a virtual destructor if you don't need one.
                    >If you really need a virtual destructor,
                    >you will need virtual constructors as well.[/color]

                    Nonsense. They aren't paired requirements.
                    [color=blue]
                    >No function should ever delete an object
                    >that was allocated by the calling function[/color]

                    ITYM "programs using pointers need a consistent, well-defined ownership
                    model"
                    [color=blue]
                    >and,
                    >in C++, it is poor programming practice
                    >to require the calling program
                    >to explicitly delete an object through a pointer
                    >returned by a function that allocated the object.[/color]

                    If the caller doesn't delete the object, what does, and how would it
                    obviate the need for a virtual dtor?

                    You've just condemned many kinds of smart pointer, garbage collection,
                    factory patterns, and much more, as "poor programming practice."

                    Personally I shall continue to use them whenever it's appropriate.

                    --
                    Richard Herring

                    Comment

                    • E. Robert Tisdale

                      #11
                      Re: Virtual destructor

                      Dave Rahardja wrote:
                      [color=blue]
                      > E. Robert Tisdale wrote:
                      >[color=green]
                      >>pmizzi wrote:
                      >>[color=darkred]
                      >>>When I compile my program with the -ansi -Wall -pedantic flags,
                      >>>I get this warning: `class vechile' has virtual functions
                      >>>but non-virtual destructor,[/color]
                      >>
                      >>It's a warning. Ignore it.[/color]
                      >
                      > Famous last words. Warnings should be dealt with, or at least documented,
                      > because they indicate an error-prone construct.[/color]

                      Not necessarily. Like lint:



                      most modern C++ compilers will warn you
                      about perfectly acceptable constructs
                      that might contain errors:
                      [color=blue]
                      > cat f.cc[/color]
                      void f(void) {
                      int a[2][2] = { 0, 1, 2, 3 };
                      }
                      [color=blue]
                      > g++ -Wall -ansi -pedantic -c f.cc[/color]
                      f.cc: In function ‘void f()’:
                      f.cc:2: warning: missing braces around initializer
                      f.cc:2: warning: unused variable ‘a’
                      [color=blue]
                      > Destructors should almost always be declared virtual.
                      > Without virtual destructors,
                      > derived classes stand a good chance of not getting destroyed correctly.[/color]

                      How?
                      [color=blue][color=green]
                      >>If you really need a virtual destructor,
                      >>you will need virtual constructors as well.[/color]
                      >
                      > Virtual destructors and "virtual constructors" (vis-a-vis Stroustroup) solve
                      > very different problems. Don't confuse them with each other.
                      >[color=green]
                      >>No function should ever delete an object
                      >>that was allocated by the calling function and,
                      >>in C++, it is poor programming practice
                      >>to require the calling program
                      >>to explicitly delete an object through a pointer
                      >>returned by a function that allocated the object.[/color]
                      >
                      > Nonsense. std::auto_ptr uses that idiom.
                      >
                      > There are situations where an Orchestrator may construct an object on the heap
                      > derived from some base class, then passes a reference to that object on the
                      > heap to a helper object as a reference to the base class. The helper object
                      > then "owns" and manages that object, and will destruct it on its own schedule.[/color]

                      Please show us an example.
                      [color=blue]
                      > There's nothing wrong with that pattern.[/color]

                      Except that it is error prone.

                      Comment

                      • E. Robert Tisdale

                        #12
                        Re: Virtual destructor

                        Richard Herring wrote:
                        [color=blue]
                        > Personally I shall continue to use them whenever it's appropriate.[/color]

                        Unfortunately, you will continue to use them
                        whether they are appropriate or not
                        because you don't know when they are appropriate
                        and when they are not appropriate.

                        Comment

                        • Marcus Kwok

                          #13
                          Re: Virtual destructor

                          Dave Rahardja wrote:[color=blue][color=green]
                          >> Destructors should almost always be declared virtual.
                          >> Without virtual destructors,
                          >> derived classes stand a good chance of not getting destroyed correctly.[/color][/color]

                          E. Robert Tisdale <E.Robert.Tisda le@jpl.nasa.gov > wrote:[color=blue]
                          > How?[/color]

                          How about:


                          #include <iostream>

                          class Base
                          {
                          public:
                          Base() : base_data(new int[5]) { std::cout << "B()\n"; }
                          ~Base() { std::cout << "~B()\n"; delete [] base_data; }
                          protected:
                          int* base_data;
                          };

                          class Derived : public Base
                          {
                          public:
                          Derived() : derived_data(ne w int[6]) { std::cout << "D()\n"; }
                          ~Derived() { std::cout << "~D()\n"; delete [] derived_data; }
                          protected:
                          int* derived_data;
                          };

                          int main()
                          {
                          Base* b = new Derived;
                          delete b;

                          return 0;
                          }


                          Output is:
                          B()
                          D()
                          ~B()

                          which says that the derived class's destructor does not get called, thus
                          derived_data does not get destroyed properly.

                          --
                          Marcus Kwok

                          Comment

                          • E. Robert Tisdale

                            #14
                            Re: Virtual destructor

                            Marcus Kwok wrote:
                            [color=blue]
                            > Dave Rahardja wrote:
                            >[color=green][color=darkred]
                            >>> Destructors should almost always be declared virtual.
                            >>> Without virtual destructors,
                            >>> derived classes stand a good chance of not getting destroyed correctly.[/color][/color]
                            >
                            > E. Robert Tisdale wrote:
                            >[color=green]
                            >>How?[/color]
                            >
                            > How about:
                            >
                            > #include <iostream>
                            >
                            > class Base {
                            > public:
                            > Base(void): base_data(new int[5]) { std::cout << "B(void)\n" ; }
                            > ~Base(void) { std::cout << "~B(void)\n "; delete [] base_data; }
                            > protected:
                            > int* base_data;
                            > };
                            >
                            > class Derived: public Base {
                            > public:
                            > Derived(void): derived_data(ne w int[6]) { std::cout << "D(void)\n" ; }
                            > ~Derived(void) { std::cout << "~D(void)\n "; delete [] derived_data; }
                            > protected:
                            > int* derived_data;
                            > };
                            >
                            > int main(int argc, char* argv[]) {
                            > Base* b = new Derived;
                            > delete b;
                            > return 0;
                            > }
                            >
                            > Output is:
                            >
                            > B(void)
                            > D(void)
                            > ~B(void)
                            >
                            > which says that the derived class's destructor does not get called,
                            > thus derived_data does not get destroyed properly.[/color]

                            That's just a bad example.
                            The programmer (you) did something stupid and unnecessary.
                            How about:
                            [color=blue]
                            > cat main.cc[/color]
                            #include <iostream>

                            class Base {
                            protected:
                            int* base_data;
                            public:
                            Base(void): base_data(new int[5]) {
                            std::cout << "B(void)" << std::endl; }
                            ~Base(void) {
                            std::cout << "~B(void)" << std::endl;
                            delete [] base_data; }
                            };

                            class Derived: public Base {
                            protected:
                            int* derived_data;
                            public:
                            Derived(void) : derived_data(ne w int[6]) {
                            std::cout << "D(void)" << std::endl; }
                            ~Derived(void) {
                            std::cout << "~D(void)" << std::endl;
                            delete [] derived_data; }
                            };

                            int main(int argc, char* argv[]) {
                            Derived* d = new Derived;
                            delete d;
                            return 0;
                            }
                            [color=blue]
                            > g++ -Wall -ansi -pedantic -o main main.cc
                            > ./main[/color]
                            B(void)
                            D(void)
                            ~D(void)
                            ~B(void)

                            But, of course, allocation from free store was unnecessary.
                            You should have substituted:

                            int main(int argc, char* argv[]) {
                            Derived d;
                            return 0;
                            }

                            C++ is *not* Java.
                            You don't have a garbage collector to clean up after you.
                            This means that you should avoid allocating free storage explicity
                            and use automatic storage instead.
                            If you find yourself in a situation
                            where you *must* allocate free storage
                            (you have not shown us such a case yet),
                            then you must be more careful about how you do it.
                            An unnecessary virtual destructor might be *very* expensive
                            if your C++ compiler cannot optimize it away.

                            Comment

                            • Marcus Kwok

                              #15
                              Re: Virtual destructor

                              E. Robert Tisdale <E.Robert.Tisda le@jpl.nasa.gov > wrote:[color=blue]
                              > That's just a bad example.
                              > The programmer (you) did something stupid and unnecessary.[/color]

                              What is stupid about using a base pointer to point to a derived type? I
                              hear it's pretty common when using polymorphism.
                              [color=blue]
                              > How about:
                              >[color=green]
                              > > cat main.cc[/color]
                              > #include <iostream>
                              >
                              > class Base {
                              > protected:
                              > int* base_data;
                              > public:
                              > Base(void): base_data(new int[5]) {
                              > std::cout << "B(void)" << std::endl; }
                              > ~Base(void) {
                              > std::cout << "~B(void)" << std::endl;
                              > delete [] base_data; }
                              > };
                              >
                              > class Derived: public Base {
                              > protected:
                              > int* derived_data;
                              > public:
                              > Derived(void) : derived_data(ne w int[6]) {
                              > std::cout << "D(void)" << std::endl; }
                              > ~Derived(void) {
                              > std::cout << "~D(void)" << std::endl;
                              > delete [] derived_data; }
                              > };
                              >
                              > int main(int argc, char* argv[]) {
                              > Derived* d = new Derived;
                              > delete d;
                              > return 0;
                              > }
                              >[color=green]
                              > > g++ -Wall -ansi -pedantic -o main main.cc
                              > > ./main[/color]
                              > B(void)
                              > D(void)
                              > ~D(void)
                              > ~B(void)
                              > But, of course, allocation from free store was unnecessary.
                              > You should have substituted:
                              >
                              > int main(int argc, char* argv[]) {
                              > Derived d;
                              > return 0;
                              > }[/color]

                              I just posted a minimal example demonstating the problem. What if the
                              class had a lot of data, and we needed a lot of objects of this type,
                              such that automatic variables are larger than the available stack space?
                              [color=blue]
                              > C++ is *not* Java.[/color]

                              I never said it was.
                              [color=blue]
                              > You don't have a garbage collector to clean up after you.
                              > This means that you should avoid allocating free storage explicity
                              > and use automatic storage instead.
                              > If you find yourself in a situation
                              > where you *must* allocate free storage
                              > (you have not shown us such a case yet),
                              > then you must be more careful about how you do it.[/color]

                              If you correctly implement RAII then it becomes less of an issue.
                              [color=blue]
                              > An unnecessary virtual destructor might be *very* expensive
                              > if your C++ compiler cannot optimize it away.[/color]

                              --
                              Marcus Kwok

                              Comment

                              Working...