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
Looks like that’s what’s happening. For posterity, and because I learnt this the hard way , 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')
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.
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++.
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.
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)
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.
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.