Welcome, Guest
Guest Settings
Help

Thread: TStrings::Equals breaks the virtual inheritance. Why ?



Permlink Replies: 3 - Last Post: May 22, 2017 1:49 PM Last Post By: Remy Lebeau (Te... Threads: [ Previous | Next ]
Jan Dijkstra

Posts: 198
Registered: 11/4/99
TStrings::Equals breaks the virtual inheritance. Why ?
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 18, 2017 5:06 AM
When I look at TObject, it has a virtual Equals member (taking a TObject as rhs parameter), so derived classes can implement what's needed to determine if the contents of two objects is equal. However, TStrings breaks this, and defines an overloaded, non-virtual member, which takes a TStrings as rhs parameter.

Right now I'm implementing several derived classes, inheriting both from TStringList and TCustomComboBoxStrings, and this breaking with normal inheritance rules causes me a lot of headaches to circumvent.

So, my question is: Why does the TStrings class break with regular inheritance rules? Why not simply implement the Equals with the TObject parameter as the routine to check if the contents of two string lists are equal? What's the design reason of doing this?

If normal inheritance were followed, I merely had to implement the Equals routine on those classes that I wanted modified behaviour for. But, as it stands now, I'm jumping through a lot of hoops to make sure I bypass every TStrings (derived) class in the RTL, even in a declaration as an object pointer, because as soon as a call ends up in one of the RTL/VCL TStrings classes, the virtual chain is out the window, and TStrings::Equals is called, ignoring any derived virtual Equals implementations.

I guess a small example of what I'm on about is in order, leaving out all the fluf
class TMyStrings : public TStringList
{
public:
  virtual bool Equals (TObject *rhs);
};
 
TMyStrings *lhs = new TMyStrings;
TStringList  *rhs = new TStringList;
 
TObject *s1 = lhs;
TStringList *s2 = lhs;
 
bool e1 = s1->Equals (rhs);
bool e2 = s2->Equals (rhs);

The s1 case invokes a virtual call to Equals, which will correctly end up in TMyStrings::Equals.
The s2 case invokes a static linked call to TStrings::Equals.

I know why this difference in result occurs. That's not the question. The question is why the TStrings class is designed in a way that this result occurs in the first place.
Greg Reese

Posts: 67
Registered: 7/15/05
Re: TStrings::Equals breaks the virtual inheritance. Why ?
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 18, 2017 6:39 AM   in response to: Jan Dijkstra in response to: Jan Dijkstra
Jan Dijkstra wrote:
I know why this difference in result occurs. That's not the question. The question is why the TStrings class is designed in a way that this result occurs in the first place.

Jan,
Do a Google search on "hidesbase". In the result, click on "Borland C++ Builder 6 Developer's Guide". That should display page 121 of the book, which contains information that may help you.
Greg Reese
Jan Dijkstra

Posts: 198
Registered: 11/4/99
Re: TStrings::Equals breaks the virtual inheritance. Why ?
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 18, 2017 6:53 AM   in response to: Greg Reese in response to: Greg Reese
Greg Reese wrote:
Jan Dijkstra wrote:
I know why this difference in result occurs. That's not the question. The question is why the TStrings class is designed in a way that this result occurs in the first place.

Jan,
Do a Google search on "hidesbase". In the result, click on "Borland C++ Builder 6 Developer's Guide". That should display page 121 of the book, which contains information that may help you.
Greg Reese

That's not what I asked. I know what hidesbase is a what it does.

What I want to know, is why the class designers have decided to implement a non virtual, overloaded Equals member in TStrings, instead of just implementing the virtual one that gets inherited from TObject.

It makes it damn near impossible to implement derived classes from the TStrings branch correctly.
Remy Lebeau (Te...


Posts: 8,075
Registered: 12/23/01
Re: TStrings::Equals breaks the virtual inheritance. Why ?
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 22, 2017 1:44 PM   in response to: Jan Dijkstra in response to: Jan Dijkstra
Jan Dijkstra wrote:
So, my question is: Why does the TStrings class break with regular inheritance rules?

The TStrings::Equals() method predates the TObject::Equals() method by a decade. TStrings::Equals() has existed since at least Delphi/C++Builder 5 (if not earlier). The TObject::Equals() method was added in Delphi/C++Builder 2009, at which time TStrings::Equals() was re-declared as 'reintroduce' (HIDESBASE in C++). So clearly someone made a conscious decision to keep the existing TStrings::Equals() implementation instead of change it to override TObject::Equals(). Whether that was an oversight or intentional, it has been in the product ever since.

If normal inheritance were followed, I merely had to implement the Equals routine on those classes that I wanted modified behaviour for. But, as it stands now, I'm jumping through a lot of hoops to make sure I bypass every TStrings (derived) class in the RTL, even in a declaration as an object pointer, because as soon as a call ends up in one of the RTL/VCL TStrings classes, the virtual chain is out the window, and TStrings::Equals is called, ignoring any derived virtual Equals implementations.

You can force the TObject::Equals() virtual implementation to be called by type-casting any object pointer to TObject* first. Of course, since TStrings does not override TObject::Equals(), that is not much help. So, for any given pair of TStrings* pointers, it might be easiest just to see if either object overrides TObject::Equals() and if so then call it, otherwise call TStrings::Equals() instead. For example:

bool OverridesTObjectEquals(TObject *Obj)
{
    typedef bool __fastcall (__closure *TEquals)(TObject*);
    TEquals Impl = &(Obj->Equals);
    TClass ClassTObject = Obj->ClassType();
    while ((ClassTObject) && (ClassTObject != __classid(TObject)))
        ClassTObject = ClassTObject->ClassParent();
    TEquals Base = &(reinterpret_cast<TObject*>(&ClassTObject)->Equals);
    return (reinterpret_cast<TMethod&>(Impl).Code != reinterpret_cast<TMethod&>(Base).Code);
}


Then you can do this:

bool AreObjectsEqual(TObject *Object1, TObject *Object2)
{
    return Object1->Equals(Object2);
}
 
bool AreObjectsEqual(TStrings *Strings1, TStrings *Strings2)
{
    if (OverridesTObjectEquals(Strings1))
        return static_cast<TObject*>(Strings1)->Equals(Strings2);
 
    if (OverridesTObjectEquals(Strings2))
        return static_cast<TObject*>(Strings2)->Equals(Strings1);
 
    return Strings1->Equals(Strings2);
}


Or this:

bool AreObjectsEqual(TObject *Object1, TObject *Object2)
{
    TStrings *Strings1 = dynamic_cast<TStrings*>(Object1);
    TStrings *Strings2 = dynamic_cast<TStrings*>(Object2);
 
    if ((Strings1) && (Strings2))
    {
        if (OverridesTObjectEquals(Strings1))
            return static_cast<TObject*>(Strings1)->Equals(Strings2);
 
        if (OverridesTObjectEquals(Strings2))
            return static_cast<TObject*>(Strings2)->Equals(Strings1);
 
        return Strings1->Equals(Strings2);
    }
 
    return Object1->Equals(Object2);
}


virtual bool Equals (TObject *rhs);

That is not overridding the virtual TObject::Equals() method, as you are missing the __fastcall calling convention:

virtual bool __fastcall Equals (TObject *rhs);


I know why this difference in result occurs. That's not the question. The question is why the TStrings class is designed in a way that this result occurs in the first place.

Probably to not break the existing interface that has existed for over a decade.

--
Remy Lebeau (TeamB)
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02