Jump to content

  • Log In with Google      Sign In   
  • Create Account

We're offering banner ads on our site from just $5!

1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


Inheritance question


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
11 replies to this topic

#1 george7378   Members   -  Reputation: 1260

Like
0Likes
Like

Posted 25 February 2014 - 11:36 AM

Hi!

 

I'm writing a program with a base class and multiple derived classes which are stored in a vector. I think it's easiest if I just post a barebones version to demonstrate my problem:

#include <iostream>

#include <vector>



using namespace std;



class Base

{

public:



    Base(){};



    virtual void identify() const

    {

        cout << "Base" << endl;

    }

};



class Derived1 : public Base

{

public:



    Derived1(){};



    void identify() const

    {

        cout << "Derived1" << endl;

    }

};



class Derived2 : public Base

{

public:



    Derived2(){};



    void identify() const

    {

        cout << "Derived2" << endl;

    }

};



int main()

{

    //Static

    vector <Base> vec;

    vec.push_back(Derived1());

    vec.push_back(Derived2());



    vec[0].identify();

    vec[1].identify();



    //Dynamic

    vector <Base *> vecdyn;

    vecdyn.push_back(new Derived1());

    vecdyn.push_back(new Derived2());



    vecdyn[0]->identify();

    vecdyn[1]->identify();



    while (!vecdyn.empty())

    {

        Base *element = vecdyn.back();

        vecdyn.pop_back();

        delete element;

    }



    cin.get();

    return 0;

}

Running the above program gives me this result:

 

Base

Base

Derived1

Derived2

 

...so the Derived1 and Derived2 objects stored in the static vector identify themselves as Base objects while the ones stored in the dynamic vector identify themselves as their respective derive classes. Why are they doing this, and is there a way to make the static objects identify themselves as derived classes rather than base ones?

 

Much appreciated smile.png


Edited by george7378, 25 February 2014 - 11:37 AM.


Sponsor:

#2 BitMaster   Crossbones+   -  Reputation: 4276

Like
7Likes
Like

Posted 25 February 2014 - 11:52 AM

The problem is well known as slicing and there is no way to avoid that without storing pointers instead of instances.

 

However, modern C++ has plenty of tools to avoid the memory management pitfalls of that (std::unique_ptr/std::shared_ptr among others).



#3 Álvaro   Crossbones+   -  Reputation: 13698

Like
4Likes
Like

Posted 25 February 2014 - 11:59 AM

What BitMaster said. I would use std::vector<std::unique_ptr<Base>>.

 

Also, your base classes should always have a virtual destructor.



#4 frob   Moderators   -  Reputation: 22294

Like
4Likes
Like

Posted 25 February 2014 - 11:59 AM

A longer description of the problems.

 

When you have a base object rather than a base pointer, you are telling the compiler to make certain assumptions. When you use the type directly the language assumes you are not invoking certain behaviors, that the object really is the type you specified and not a derived type.

 

Also note that you don't have a virtual destructor.  A destructor should either be public and virtual (meaning you can use it as a base class) or protected and non-virtual (meaning you cannot inherit from it.) This has the same basic problem, if you don't do the destructor properly the object can be incompletely destroyed.


Check out my book, Game Development with Unity, aimed at beginners who want to build fun games fast.

Also check out my personal website at bryanwagstaff.com, where I write about assorted stuff.


#5 ChaosEngine   Crossbones+   -  Reputation: 2477

Like
1Likes
Like

Posted 25 February 2014 - 03:03 PM


Also note that you don't have a virtual destructor.  A destructor should either be public and virtual (meaning you can use it as a base class) or protected and non-virtual (meaning you cannot inherit from it.) This has the same basic problem, if you don't do the destructor properly the object can be incompletely destroyed.

 

I know this is the correct thing to do, and I am in no way advocating NOT making it virtual, but in this case does it actually matter in practice?

i.e. Derived has no extra memory overhead, so the allocated space for Base is released? Just a question.


if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

#6 Álvaro   Crossbones+   -  Reputation: 13698

Like
1Likes
Like

Posted 25 February 2014 - 07:31 PM

Also note that you don't have a virtual destructor.  A destructor should either be public and virtual (meaning you can use it as a base class) or protected and non-virtual (meaning you cannot inherit from it.) This has the same basic problem, if you don't do the destructor properly the object can be incompletely destroyed.

 
I know this is the correct thing to do, and I am in no way advocating NOT making it virtual, but in this case does it actually matter in practice?
i.e. Derived has no extra memory overhead, so the allocated space for Base is released? Just a question.


The fact that Derived has nothing extra to do in the destructor is a detail that Base shouldn't be aware of. In particular, if that changes in the future, only the code in Derived should have to change.

#7 ChaosEngine   Crossbones+   -  Reputation: 2477

Like
1Likes
Like

Posted 25 February 2014 - 07:49 PM

I know this is the correct thing to do, and I am in no way advocating NOT making it virtual, but in this case does it actually matter in practice?
i.e. Derived has no extra memory overhead, so the allocated space for Base is released? Just a question.


The fact that Derived has nothing extra to do in the destructor is a detail that Base shouldn't be aware of. In particular, if that changes in the future, only the code in Derived should have to change.


Yep, that's why I said making it virtual was the Right Thing To Do™.

My question was whether, in this particular instance, a non-virtual destructor would break the code, i.e. is the behaviour undefined?
It was an academic question.
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

#8 Álvaro   Crossbones+   -  Reputation: 13698

Like
3Likes
Like

Posted 25 February 2014 - 08:59 PM

I believe destructing a Derived through a pointer to Base is indeed undefined behavior if Base doesn't have a virtual destructor.

To make matters less clear smile.png , this does work:
#include <iostream>
#include <memory>

struct Base {
  Base() {
    std::cout << "Base\n";
  }

  ~Base() {
    std::cout << "~Base\n";
  }
};

struct Derived : Base {
  Derived() {
    std::cout << "Derived\n";
  }

  ~Derived() {
    std::cout << "~Derived\n";
  }
};

int main() {
  std::shared_ptr<Base> p(new Derived);
  // The constructor of std::shared_ptr<Base> is a template and it will remember to call the correct destructor for the type passed!
}

Edited by Álvaro, 25 February 2014 - 08:59 PM.


#9 frob   Moderators   -  Reputation: 22294

Like
3Likes
Like

Posted 25 February 2014 - 09:20 PM

If the incorrect function is never called, yes, it is well-defined.

However, rather than just hoping it is correct and that nobody ever misuses it, or through documentation and verification and luck never doing an incorrect function call, it is just so much easier to follow the well-established convention and remove all doubt.

Check out my book, Game Development with Unity, aimed at beginners who want to build fun games fast.

Also check out my personal website at bryanwagstaff.com, where I write about assorted stuff.


#10 george7378   Members   -  Reputation: 1260

Like
0Likes
Like

Posted 26 February 2014 - 03:32 AM

Thanks for the responses, so am I right in thinking that a virtual destructor should be something like this in the base class:

virtual ~Base(){};
...although I shouldnt need a destructor unless I allocate dynamic memory within the class, right?

#11 King Mir   Members   -  Reputation: 2032

Like
0Likes
Like

Posted 26 February 2014 - 03:54 AM

If you call delete on the base pointer, the destructor needs to be virtual. As far as I'm aware, there is no special rule for if the derived destructor is trivial, so you still need to call the matching destructor of the type that the object was constructed as. Now as frob says, if you don't call delete or explicitly invoke the base constructor, then having a virtual destructor is not required.

Take note: in the vector<Base> example, the destructor does not need to be virtual, because you never destroy the derived class through the base pointer. The temporary derived object has its destructor called after the base object is built without using virtual dispatch.

Edited by King Mir, 26 February 2014 - 03:58 AM.


#12 BitMaster   Crossbones+   -  Reputation: 4276

Like
1Likes
Like

Posted 26 February 2014 - 04:04 AM



virtual ~Base(){};
...although I shouldnt need a destructor unless I allocate dynamic memory within the class, right?


No. A simplified real world example I once encountered was

class Base { };

class Derived : public Base { std::string m_myString; }

// ...
Base* p = new Derived;
// ...
delete p;
The std::string inside Derived never gets destroyed. If it allocated memory (which it likely did) then that memory is not freed.

Even beyond indirect memory allocation you can get problems. File handles might not get closed. Other scarce system resources (like sockets) might remain open until your application terminates.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS