Constructor and Destructor Special Behavior

Tags: #<Tag:0x00007fc0d9664800>

Constructors and Destructors seem to behave in a special way. If you derive a class that has it’s own constructor/destructor, they seem to automagically become virtual with an implied parent call at the start of the derived constructor and end of the destructor code. Is this a correct assumption?

Example:

  PROGRAM
  MAP
  END
c1                  CLASS,TYPE
Construct             PROCEDURE()
Destruct              PROCEDURE()
                    END
c2                  CLASS(c1),TYPE
Construct             PROCEDURE()
Destruct              PROCEDURE()
                    END
c3 &c2
  CODE
  c3 &= new c2
  dispose(c3)
c1.Construct        PROCEDURE()
  CODE
  STOP('c1 Construct')
c2.Construct        PROCEDURE()
  CODE
  STOP('c2 Construct')  
c1.Destruct        PROCEDURE()
  CODE
  STOP('c1 Destruct')
c2.Destruct        PROCEDURE()
  CODE
  STOP('c2 Destruct')  

So in this case you see c1 construct, c2 construct, c2 destruct, c1 destruct

1 Like

Looks like that’s what’s happening. For posterity, and because I learnt this the hard way :slight_smile: , I’ll add my 2 cents to show that sometimes you really might need that VIRTUAL on a destructor.

The place I learned that VIRTUAL was needed was when c3 was a reference of c1 (the base class), but was assigned an object of c2 (the derived class).

In this example, c2.Destruct is never called because there’s no VIRTUAL:

 PROGRAM
  MAP
  END
c1                  CLASS,TYPE
Construct             PROCEDURE()
Destruct              PROCEDURE()
                    END
c2                  CLASS(c1),TYPE
Construct             PROCEDURE()
Destruct              PROCEDURE()
                    END
c3 &c1         !A reference to the BASE class
  CODE
  c3 &= new c2 !But assigned to the DERIVED class
  dispose(c3)
c1.Construct        PROCEDURE()
  CODE
  STOP('c1 Construct')
c2.Construct        PROCEDURE()
  CODE
  STOP('c2 Construct')  
c1.Destruct        PROCEDURE()
  CODE
  STOP('c1 Destruct')
c2.Destruct        PROCEDURE()
  CODE
  STOP('c2 Destruct')  

But in this example, both destructors are called because there IS a VIRTUAL:

 PROGRAM
  MAP
  END
c1                  CLASS,TYPE
Construct             PROCEDURE()
Destruct              PROCEDURE(),VIRTUAL
                    END
c2                  CLASS(c1),TYPE
Construct             PROCEDURE()
Destruct              PROCEDURE(),VIRTUAL
                    END
c3 &c1
  CODE
  c3 &= new c2
  dispose(c3)
c1.Construct        PROCEDURE()
  CODE
  STOP('c1 Construct')
c2.Construct        PROCEDURE()
  CODE
  STOP('c2 Construct')  
c1.Destruct        PROCEDURE()
  CODE
  STOP('c1 Destruct')
c2.Destruct        PROCEDURE()
  CODE
  STOP('c2 Destruct')
2 Likes

I add VIRTUAL to all my destructors, just in case.

Here’s an email response from Alexey 2000.06.26 with more of a description about the behavior. In my case, the parent class had no destructor originally, so I was confused when my explicitly declared destructor in the derived class was never called.

Hello,

From: Jeff Slarve
Date: Mon, 26 Jun 2000 15:19:38 -0700

Is the attached example expected behavior?

YES

I can sort of understand, if it is, but it is kind of scary.
If so, maybe the documentation team should clarify the docs on DESTRUCT().

This behavior has nothing related with DESTRUCT (except the fact that
DISPOSE calls DESTRUCT implicitly). This is demonstration how early
binding works.

If a method is declared without the VIRTUAL (or DERIVED) attribute the
compiler generates a code for the direct call to specified method of the
specified instance of the class. The class type is a type used for the
instance, reference variable or function parameter declaration. So, if
the reference variable is declared as

Ref &MostBaseClass

any calls to not virtual methods

Ref.SomeNotVirtualMethod()

will invoke methods of the MostBaseClass even if at the call time Ref
contains a reference to an instance of some derived class which overrides
SomeNotVirtualMethod method.

If a method is declared using the VIRTUAL or DERIVED attribute, the
compiler uses the late binding. I.e., it retrieves an address of the
method to be called at run time from the particular instance of the
object:

Ref.SomeVirtualMethod()

In this case the address of particular variant of the SomeVirtualMethod
is resolving from the object referencing by the Ref variable at the call
time.

I guess, all this is described somewhere in User Guide or other companion
documentation.

I got around it by putting a VIRTUAL destruct in the parent class, but I
didn’t really need a destruct there.

Your solution is not a work around. It’s how it should be done.

Alexey Solovjev

1 Like

That’s a really interesting explanation

Yup, I had almost the same conversation with Alexey. :wink:

This is not unique to Clarion you will find it in C++ also so I would guess it is a common OOP thing. It seems like a lot of Clarion’s underlying OOP logic is from TopSpeed C++.

e.g. The Old New Thing post " When should your destructor be virtual?".

His example is similar to Jeff that you have a Derived Class but your Reference is to the Bass class. Then if you Dispose or Destruct using your Reference the Virtual is required to call up to the Derived.

I prefer an example like below will named things

AnimalType  CLASS,TYPE
MammalType  CLASS(AnimalType),TYPE
BigCatType  CLASS(MammalType),TYPE

ZooQ QUEUE
Animal  &AnimalType             !Note Q has Ref to Base Class

ZooQ.Animal &= NEW(BigCatType)  !Note New is for Derived Big Cat but &= Animal Base 
...

DISPOSE( ZooQ.Animal )  !<-- Dispose Base Class requires Virtual Destructors to call BigCat Destruct

The VS C++ compiler will warn you of the problem, but it is for a “potential problem”. Another point that this is not just for Clarion.

The CONSTRUCT virtual calling can be turned off by adding REPLACE which then calls the Derived class CONSTRUCT only. In that method you could explicitly call PARENT.Construct(). I cannot think of a good use for that but I would guess it exists in C++ so SV copied that.

Reading the help I was surprised to find REPLACE also allowed for DESTRUCT. I tested and it worked, but it cannot be used if the Base Class has VIRTUAL. What REPLACE does in the below code is after C2.Destruct is called the C1.Destruct is NOT called. Take off Replace and you see C2 then C1 Destruct called.

ConREPLACE_DesREPLACE_Test     PROCEDURE()
MsgLog  ANY
c1                  CLASS,TYPE
Construct             PROCEDURE()   !Not Called due to REPLACE
Destruct              PROCEDURE()   !Not Called due to REPLACE
                    END
c2                  CLASS(c1),TYPE
Construct             PROCEDURE(),REPLACE
Destruct              PROCEDURE(),REPLACE
                    END
c3 &c2
  CODE
  c3 &= new c2 
  dispose(c3)
  Message('-{50}'& MsgLog,'xConREPLACE_DesREPLACE_Test',,,,MSGMODE:CANCOPY)  
  RETURN

c1.Construct        PROCEDURE()
  CODE
  MsgLog = MsgLog & '<13,10>c1 Construct'
c2.Construct        PROCEDURE()
  CODE
  MsgLog = MsgLog & '<13,10>c2 Construct REPLACE'
 !  PARENT.Construct()  !test parent call

c1.Destruct        PROCEDURE()
  CODE
  MsgLog = MsgLog & '<13,10>c1 Destruct'
c2.Destruct        PROCEDURE()
  CODE
  MsgLog = MsgLog & '<13,10>c2 Destruct REPLACE'   
  !  PARENT.Destruct()  !test parent call 

Result:

c2 Construct REPLACE
c2 Destruct REPLACE

One other detail is on the Derived Class DESTRUCT instead of VIRTUAL you can use DERIVED.

1 Like

I guess the point I was trying to convey with that was that not only should you add the VIRTUAL attribute to your destructor, but you should also ensure that you have a destructor in the first place, to ensure that derived destructors can get called (if that’s what you would wish)

1 Like

In my tests if you add a Destructor to a derived class you get an error if the Base class does not have a Destructor. So you could add a base class virtual Destructor with no code to let it work.

Edit 12/20:
The above is WRONG, see below reply.

1 Like

I’m not seeing any error here. It’s just that the derived class’s destructor doesn’t fire. Maybe something else was in play?

 PROGRAM
  MAP
  END
c1                  CLASS,TYPE
Construct             PROCEDURE()
                    END
c2                  CLASS(c1),TYPE
Construct             PROCEDURE()
Destruct              PROCEDURE(),VIRTUAL
                    END
c3 &c1
  CODE
  c3 &= new c2
  dispose(c3)
c1.Construct        PROCEDURE()
  CODE
  STOP('c1 Construct')
c2.Construct        PROCEDURE()
  CODE
  STOP('c2 Construct')  
c2.Destruct        PROCEDURE()
  CODE
  STOP('c2 Destruct')

You are correct it works. My Bad.

I removed the “C1 DESTRUCT” declaration line but forgot to delete the definition code. The error was Missing procedure definition: DESTRUCT(C1). Once I delete procedure code the Virtual on C2 works.

If I try to make the C2 Destructor DERIVED instead of VIRTUAL then I get the expected error that the Parent Class Virtual method cannot be found. You would also get an error if the Parent was not VIRTUAL.

1 Like