[C++] Abstract classes & con- and destructors

Started by
6 comments, last by Xai 17 years, 7 months ago
I've read several sources regarding abstract classes, but I can't find a clear statement about it. My question is: Will all constructors and desctructors of super classes be called when calling the constructor and destructor at most expanded/derived subclass?

  class Colour {
  public:
    Colour();  // Constructor
    virtual ~Colour();  // Destructor
  };

  class Red : public Colour {
  public:
    Red();  // Constructor
    virtual ~Red(); // Destructor
  };

  class LightRed : public Red {
  public:
    LightRed();  // Constructor
    ~LightRed(); // Destructor
  };

Thus, when creating an object of the LightRed class, will all previous constructors (Red and Colour) be called? And does it counts for the destructor as well? And what happens if the constructor of a derivative of an abstract class has additional constructor parameters/arguments? How should that be handled? I know in Delphi, one should call in the constructor of a subclass "Inherited Init;" and in the destructor of a subclass "Inherited Free;" with virtual functions. Further I was wondering when using inheritance: Do different objects exist at run-time according some order and jumps through those different (methods of) objects? Or are different classes merged into one class at compile time with the name of the most expanded/derived class (They should use the same VMT order as the base class if one will typecast each derivative of the base class)? Thanks in advance.
Be creative, don't copy...Greets from Holland!
Advertisement
A derived class uses its parents default constructor to initialise the base part of it unless you specify otherwise. If a class derives from a base that has no default constructor and has some user speicifed one, then you must specify which one.

If you dont know, you can specify a different base constructor like so:

class Base{public:   Base();   Base( int ); };class UsesDefault : public Base{public:    UsesDefault()    {           // code    }};class VerboseUsesDefault : public Base{public:    VerboseUsesDefault ()    :       Base() // this is not required, the compiler will do it anyway    {           // code    }};class UsesIntConstructor : public Base{public:    UsesIntConstructor ()    :        Base(42) // use Base's "int" constructor    {           // code    }};


With a virtual destructor, all the destructors will be called, starting with the most derived and ending at the parents.

With a non virtual destructor, all of them will be called except when you delete a derived class via a pointer to a base class. Which is why all classes that are used as base classes should have a virtual destructor.

Quote:
Further I was wondering when using inheritance: Do different objects exist at run-time according some order and jumps through those different (methods of) objects? Or are different classes merged into one class at compile time with the name of the most expanded/derived class (They should use the same VMT order as the base class if one will typecast each derivative of the base class)?


The compiler allocates enough memory for each of the classes in the inheritance heirarchy, including any padding ( with virutal functions, there can often be padding at the start of an objet for the objects vtable This is not however guarenteed ). Multiple inheritance can make this more difficult, but the compiler ends up allocating enough space for all of them.

I'm not quite sure what you are asking when you say "and jumps through those different (methods of) objects?".
Quote:Original post by rmdeboer82
I've read several sources regarding abstract classes, but I can't find a clear statement about it. My question is:

Will all constructors and desctructors of super classes be called when calling the constructor and destructor at most expanded/derived subclass?

*** Source Snippet Removed ***

Thus, when creating an object of the LightRed class, will all previous constructors (Red and Colour) be called?
Yes. They are called in the order of least derived to most derived.
Quote:
And does it counts for the destructor as well?
Yes, as long as you use virtual destructors. They are called in the order of most derived to least derived.
Quote:

And what happens if the constructor of a derivative of an abstract class has additional constructor parameters/arguments? How should that be handled?
The abstract class doesn't need to know or care about this. The constructor of the derived class is passed some arguments. It first calls all its base classes' constructors and constructs/initialises its members (possibly according to the initialiser list), then does whatever it needs to do with the arguments. In this way, the abstract class's constructor doesn't even need to know about the derived class's constructor arguments.
edit: rip-off explains this better.
Quote:

I know in Delphi, one should call in the constructor of a subclass "Inherited Init;" and in the destructor of a subclass "Inherited Free;" with virtual functions.

Further I was wondering when using inheritance: Do different objects exist at run-time according some order and jumps through those different (methods of) objects? Or are different classes merged into one class at compile time with the name of the most expanded/derived class (They should use the same VMT order as the base class if one will typecast each derivative of the base class)?

They're usually laid out in memory into one object, but I doubt the standard has anything concrete to say about this — it's probably left to the implementation. Multiple inheritance complicates things further. The order of the virtual function table will be probably the same, but it's not necessary to know this in order to program in C++.
Quote:
Quote:
And does it counts for the destructor as well?

Yes, as long as you use virtual destructors. They are called in the order of most derived to least derived.


No, destructors are called in order from most-derived type to least-derived (base) type, regardless of whether or not the destructor is virtual. A virtual destructor is required when the dispatch of the destructor call must be based on the dynamic type rather than the static type of the object (i.e., you delete a pointer-to-base that is really a pointer-to-derived; you want to ensure that the destructor your program "starts with" is the correct one).

[Edited by - jpetrie on August 20, 2006 5:14:07 PM]
Yes, I failed to explain that properly :/
The syntax used at the end of the constructor, to call another constructor (as show by rip-off) is called the "initializer list" and can be used to specify which base class constructors should be called AS WELL AS specifying which member object constructors should be called - see example below:

The way C++ works without any initializer list is the same as if you had placed a call to each base class's default constructor in the initializer list.

Constructors will ALWAYS construct from most base to most derived - with the most derived being the class you are asking to create. There are some quirks when using multiple inheiritance and / or virtual inheiritance (which still run from most base to most dervied in each inheiritance chain, they just have more than 1 inheiritance chain to execute).

Destructors will ALWAYS from most derived to most base, with the most derived being the compile-time type you are deleting if you do not use virtual inheiritance, and the most derived being the actual run-time type of the object if using virtual inheiritance.

class GameObject{  string name;public:  GameObject() : name("unnamed") {}  GameObject(const string &name) : name(name) {}  ~GameObject() {} // this SHOULD be virtual, but for example I have intentionally made it NON-VIRTUAL};class VisibleObject : public GameObject{public:  VisibleObject() : GameObject("unnamed VisibleObject") {}  VisibleObject(const string &name) : GameObject(name) {}  virtual ~VisibleObject() {}  virtual void Render() const = 0;};class Model : public VisibleObject{public:  Model(const string &filename) {}  virtual ~Model() {}  virtual void Render() const {}};class Marine : VisibleObject{  Marine() : Model("Marine.mdl") {}  virtual ~Marine() {}};int main(){  GameObject *go1;  VisibleObject *go2;  Marine m1; // m1 gets constructed: GO() -> VO() -> Mdl("Marine.mdl") -> Ma()  // notice that the fact that Model(string) was called DOES NOT automatically call VisibleObject(string) ... it still uses the default constructor until you explicitly command it otherwise.  go1 = new Marine(); // constructed: GO() -> VO() -> Mdl("Marine.mdl") -> Ma()  go2 = new Marine(); // constructed: GO() -> VO() -> Mdl("Marine.mdl") -> Ma()  delete go1; // destroyed: ~GO()  delete go2; // destroyed: ~Ma() -> ~Mdl() -> ~VO() -> ~GO()  // notice that go1 ONLY delete from GameObject down (which in this case it just it) ... because its destructor is NON-VIRTUAL, it doesn't work correctly when used to delete objects that are derived ... it is a major bug to ever delete objects polymorphically without using a virtual destructor - now I'm sure you can see why.  // m1 gets destroyed at main end: ~Ma() -> ~Mdl() -> ~VO() -> ~GO().}
Thanks, it's clear to me. Xai, i assume
Marine : VisibleObject
, must be
Marine : Model

, right?

Quote:Original post by rip-off
I'm not quite sure what you are asking when you say "and jumps through those different (methods of) objects?".


I mean using different objects at runtime requires some management to get the behaviour of the final class in case it would work like that.

[Edited by - rmdeboer82 on August 24, 2006 4:31:49 AM]
Be creative, don't copy...Greets from Holland!
rmdeboer82 - yes, that was a copy/paste error. Now I'm sure you've got it if your catching my typos :)

As for "jumping through" other classes functions. Yes constructors and destructors automatically run through (aka execute) each version from each class, in a well defined order. Functions which are not constructors and destructors do not automatically call on base class versions, you must call the base class version in the derived class explicity if you want to.

Just to bring it home for other people reading - constructors act like this:

void MyFunction()
{
BaseClass::MyFunction();

// my code here
}

and destructors act like you've done this:

void MySecondFunction()
{
// my code here

BaseClass::MySecondFunction();
}

and if you EVER EVER EVER intend to use a base class polymorphically ... make the destructor virtual - or you'll get the craziest bugs you've ever seen.

This topic is closed to new replies.

Advertisement