August 01, 2005
Hi,

>Why is it not possible to embed the foreign object into a class with a subclass, whose destrcutor calls the cleanup procedure of the foreign object?

Because it's not just one type of object. It's a whole collection of possible sub-classes of a BaseDatabase class.

# class MySQLDatabase  : BaseDatabase { ... }
# class PGSQLDatabase  : BaseDatabase { ... }
# class SQLiteDatabase : BaseDatabase { ... }
# // and so on...

>Then because the subclass' destructor is executed before the embedding class' destructor the cleanup procedure will be executed before the foreign object is destroyed.

Yeah, it's a possible workaround. At any rate, if you are doing this, it's essentially a hack to bypass the normal destructor restrictions, which shouldn't be there in the first place.

Cheers,
--AJG.


August 01, 2005
AJG <AJG_member@pathlink.com> wrote:

>>Why is it not possible to embed the foreign object into a class with a subclass, whose destrcutor calls the cleanup procedure of the foreign object?
> 
> Because it's not just one type of object. It's a whole collection of possible sub-classes of a BaseDatabase class.
> 
> # class MySQLDatabase  : BaseDatabase { ... }
> # class PGSQLDatabase  : BaseDatabase { ... }
> # class SQLiteDatabase : BaseDatabase { ... }
> # // and so on...

I do not understand this argument. If the subclasses represent different database types, then why is the cleanup procedure the same in all cases?

>>Then because the subclass' destructor is executed before the embedding class' destructor the cleanup procedure will be executed before the foreign object is destroyed.
>
> Yeah, it's a possible workaround.

No it is not a workaround as I found out:

| The garbage collector is not guaranteed to run the destructor | for all unreferenced objects.

And this restrictions follows in a natural way from the GC.

> bypass the normal destructor
> restrictions, which shouldn't be there in the first place.

Because of the GC they must be there and if you want to guarantee the execution of the cleanup operation you have to resort to deleting explictely.

-manfred
August 01, 2005
> Because of the GC they must be there and if you want to guarantee the
> execution of the cleanup operation you have to resort to deleting
> explictely.

Or use the "auto" keyword.

Regan
August 01, 2005
> Hm... maybe this could work. What's the equivalent code using malloc? I mean, how do you call the object's constructor(s) using this method?

Hmm, as it is legacy code, i fear that it is not possible. You can look into

http://www.digitalmars.com/techtips/class_objects.html

But this is more like a hack. The correct way would be to overload operator new and delete for the class. See

http://www.digitalmars.com/d/memory.html#newdelete

And you are right, in this case there is really a need for the destructor to access sub-objects. Anyways, imagine two objects that are "linked together":

class A
{
  B b;
}

class B
{
  A a;
}

One of them has to be deleted first. Which one, that is undetermined. Thus you cannot be sure to be able to access b in A's destructor and vice versa. The GC would have to examine this kind of relationships, and they could be much more complex. That's the reason for the current rule.

Another solution, by the way: use delete on your object. In this case you *can* reference sub-objects.

Ciao
uwe
August 01, 2005
Hi,

>>>Why is it not possible to embed the foreign object into a class with a subclass, whose destrcutor calls the cleanup procedure of the foreign object?
>> 
>> Because it's not just one type of object. It's a whole collection of possible sub-classes of a BaseDatabase class.
>> 
>> # class MySQLDatabase  : BaseDatabase { ... }
>> # class PGSQLDatabase  : BaseDatabase { ... }
>> # class SQLiteDatabase : BaseDatabase { ... }
>> # // and so on...
>
>I do not understand this argument. If the subclasses represent different database types, then why is the cleanup procedure the same in all cases?

They all override a virtual void close(); finalization method to terminate the connection. In order for your solution to work, I'd have to wrap up (inherit from) every subclass in another object that calls this method as a protected member, whereas if I could reference the object in the destructor, I could just call .close() once in one single object. I don't know if that makes sense. If it doesn't could you provide an example of what you mean?

>>>Then because the subclass' destructor is executed before the embedding class' destructor the cleanup procedure will be executed before the foreign object is destroyed.
>>
>> Yeah, it's a possible workaround.
>
>No it is not a workaround as I found out:
>
>| The garbage collector is not guaranteed to run the destructor | for all unreferenced objects.

Oh, great. Yet another restriction.

>And this restrictions follows in a natural way from the GC.
>
>> bypass the normal destructor
>> restrictions, which shouldn't be there in the first place.
>
>Because of the GC they must be there and if you want to guarantee the execution of the cleanup operation you have to resort to deleting explictely.

Couldn't the GC just tag the objects that are needed and keep those from being collected until their containers' run their respective destructors?

Cheers,
--AJG.


August 01, 2005
The issue here is that when 2 objects become garbage at the same time, there is no defined order in which they are cleaned up.  So, sometimes, the code like you wrote below will work, because the holder of the reference is destroyed first; other times, it won't, because they are cleaned up in the opposite order.

The reason that this problem exists is because the GC must deal with loops; when a loop of object references goes out of scope, what gets deleted first?



*** 'auto' solution ***
One solution is to use 'auto' objects. http://digitalmars.com/d/class.html says that auto objects don't have this ordering issue.  However, I don't think that you can reassign 'auto' objects, so this is not an option if you will sometimes have to change the reference variable.



*** manual refcounting solution ***
Another solution is to find a way to make sure that the refered-to object is not garbage until after the referer dies; one way to do that is with an outside reference.  The code example below has an associative array which stores references to objects in a global; these references can be added to or removed as needed, to ensure ordering:

uint[Object] referenceTable;
void AddRef(Object o) {
  // you can write more efficient code (avoid the double lookup in
  // the AA) but this is easy to read for learning purposes.  Also,
  // this code is not thread-safe.

  if(o in referenceTable)
    referenceTable[o]++;
  else
    referenceTable[o] = 1;
}
void DelRef(Object o) {
  // again, double-lookup and thread safety issues.

  assert(o in referenceTable);
  referenceTable[o]--;
  if(referenceTable[o] == 0)
    referenceTable.remove(o);
      // don't delete, since somebody else in the world
      // might still have a reference.  Just drop this
      // reference, making it possible that this *might*
      // be garbage on the next pass.
}

class Test {
  Object obj = null;
  this () { obj = new Object(); AddRef(obj); }
  ~this() { obj.toString();     DelRef(obj); }
}

Uwe Salomon wrote:
>> I took me a while to find and reduce this to a minimal example. I was  going nuts
>> thinking it was my database abstraction layer. I think it's a bug in D.
>>
>> # // test.d
>> # class Test {
>> #     Object obj = null;
>> #     this () { obj = new Object(); }
>> #     ~this() { obj.toString();     }
>> # }
>> #
>> # void main() { new Test(); }
>>
>> Segmentation Fault w/ DMD 0.128 on linux.
> 
> 
> http://www.digitalmars.com/d/class.html#destructors
> "When the garbage collector calls a destructor for an object of a class  that has members that are references to garbage collected objects, those  references are no longer valid. This means that destructors cannot  reference sub objects."
> 
> So this is documented and correct behaviour.
> 
> Ciao
> uwe
August 01, 2005
> I could hack DDBI to auto-close the connection, but I'd rather not, since
> that's
> not its interface. In my opinion destructors are broken, and very limited.
> We
> should be able to reference sub-objects that don't do automatic cleanup
> precisely for cases like this.

I agree destructors in D are very limited. They seem to be only useful for a class that owns an external resource that must be destroyed when the object is destroyed. To me that's ok if it gives the GC enough freedom to be faster and more predictable. I can see how one could say dtors in D are broken if one expects to use them like C++ or even Java/C#. If the Database subclasses that own an external resource do not have dtors then I'd request one from the author as a safety net. A user class holding a reference to a Database should not have a dtor since those classes do not own the external resource. In any case if the database dtor ends up closing the external resource it could very well be a bug since the user code should have closed the database long before the dtor ended up running.


August 01, 2005
AJG <AJG_member@pathlink.com> wrote:

> I don't know if that makes sense. If it doesn't could you provide an example of what you mean?

We need not try to explore this any further because it will not yield a solution.

> Couldn't the GC just tag the objects that are needed and keep those from being collected until their containers' run their respective destructors?

D is built around the type of GC currently used. Especially tagging does not help in case of cyclic references.

-manfred
August 02, 2005
Generally speaking, destructors called by the garbage collector are not terribly useful, for the reasons you mentioned. Destructors called when they go out of scope (i.e. auto objects) are very useful.


August 02, 2005
Hi Walter,

>Generally speaking, destructors called by the garbage collector are not terribly useful, for the reasons you mentioned. Destructors called when they go out of scope (i.e. auto objects) are very useful.

What about getting the compiler to complain if there are sub-object member references in the destructor of a non-auto object, since they are not allowed? Would that be possible? It'd be an improvement.

Thanks,
--AJG.