Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's wrong with hiding virtual method of a base class?

I have been getting Delphi compiler warnings about Method 'Create' hides virtual method of base.

I have reviewed several Stack Overflow links (see below), and I don't understand the logic behind this warning, and why it is considered bad coding practice. I'm hoping others can help me understand

I will include some sample code:

type    
  TMachine = class(TPersistent)
  private
  public
    Horsepower : integer;
    procedure Assign(Source : TMachine);
  end;

...

procedure TMachine.Assign(Source : TMachine);
begin
  inherited Assign(Source);
  Self.Horsepower := Source.HorsePower;
end;

This causes the compiler warning.

[dcc32 Warning] Unit1.pas(21): W1010 Method 'Assign' hides virtual method of base type 'TPersistent'

I have been ignoring this warning because it didn't make any sense to me. But that got me in trouble in another way (see my other post here: Why does Delphi call incorrect constructor during dynamic object creation?) so I have decided to try to understand this better.

I know that if I use the reserved word reintroduce, the error will go away, but I have seen it repeatedly posted that this is a bad idea. As Warren P wrote here (Delphi: Method 'Create' hides virtual method of base - but it's right there), "IMHO, if you need reintroduce, your code smells horrible".

I think I understand what is meant by "hiding". As David Heffernan said here (What causes "W1010 Method '%s' hides virtual method of base type '%s'" warning?):

What is meant by hiding is that from the derived class you no longer have access to the virtual method declared in the base class. You cannot refer to it since it has the same name as the method declared in the derived class. And that latter method is the one that is visible from the derived class.

But I am somewhat confused because it seems that ancestor method is not really hidden, because a derived class can always just use the inherited keyword to call the method in the base class. So 'hidden' really means 'somewhat hidden'?

I think I also understand that using the reserved word override will prevent the compiler warning, but the procedure signature has to be the same (i.e. no newly added parameters). That I can't use that here.

What I don't understand is why hiding is something to be warned about. In my code example above, I would not want users of TMachine.Assign() to instead somehow use TPersistent.Assign(). In my extended class, I have extended needs, and therefore want to them to use the new and improved function. So it seems like hiding the older code is exactly what I want. My understanding of a virtual method is one where the correct method is called based on the actual type of an object at run time. I don't think that should have any bearing in this case.

Additional code, to be added to example code above

  TAutomobile = class(TMachine)
  public
    NumOfDoors : integer;
    constructor Create(NumOfDoors, AHorsepower : integer);
  end;

...

constructor TAutomobile.Create(ANumOfDoors, AHorsepower : integer);
begin
  Inherited Create(AHorsepower);
  NumOfDoors := ANumOfDoors;
end;

This adds new compiler warning message: [dcc32 Warning] Unit1.pas(27): W1010 Method 'Create' hides virtual method of base type 'TMachine'

I especially don't understand problems that arise with using new constructors with additional parameters. In this post (SerialForms.pas(17): W1010 Method 'Create' hides virtual method of base type 'TComponent'), the wisdom seems to be that a constructor with a different name should be introduced, e.g. CreateWithSize. This would seem to allow users to pick and choose which constructor they want to use.

And if they choose the the old constructor the extended class might be missing some needed information for creation. But if, instead, I 'hide' the prior constructor, it is somehow bad programming. Marjan Venema wrote about reintroduce in this same link: Reintroduce breaks polymorphism. Which means that you can no longer use meta classes (TxxxClass = class of Tyyy) to instantiate your TComponent descendant as its Create won't be called. I don't understand this at all.

Perhaps I need to understand polymorphism better. Tony Stark wrote in this link (What is polymorphism, what is it for, and how is it used?) that polymorphism is: "the concept of object oriented programming.The ability of different objects to respond, each in its own way, to identical messages is called polymorphism." So am I presenting a different interface, i.e. no longer an identical message, and thus this breaks polymorphism?

What am I missing? In summary, isn't hiding base code a good thing in my examples?

like image 465
kdtop Avatar asked May 26 '17 22:05

kdtop


1 Answers

The danger here is that you might call Assign on a base class reference. Because you did not use override then your derived class method is not called. You have thus subverted polymorphism.

By the principle of least surprise you should use override here, or give your derived class method a different name. The latter option is simple. The former looks like this:

type    
  TMachine = class(TPersistent)
  public
    Horsepower : integer;
    procedure Assign(Source : TPersistent); override;
  end;

...

procedure TMachine.Assign(Source : TPersistent);
begin
  if Source is TMachine then begin
    Horsepower := TMachine(Source).Horsepower;
  end else begin
    inherited Assign(Source);
  end;
end;

This allows your class to co-operate with the polymorphic design of TPersistent. Without using override that would not be possible.

Your next example, with virtual constructors is similar. The entire point of making a constructor virtual is so that you can create instances without knowing their type until runtime. The canonical example is the streaming framework, the framework that processes .dfm/.fmx files and creates objects and sets their properties.

That streaming framework relies on the virtual constructor of TComponent:

constructor Create(AOwner: TComponent); virtual;

If you want a component to work with the streaming framework, you must override this constructor. If you hide it, then the streaming framework cannot find your constructor.

Consider how the streaming framework instantiates components. It does not know about all the component classes it needs to work with. It cannot, for instance consider third party code, the code you write. The Delphi RTL cannot know about types defined there. The streaming framework instantiates components like this:

type
  TComponentClass = class of TComponent;

var
  ClassName: string;
  ClassType: TComponentClass;
  NewComponent: TComponent;

....
ClassName := ...; // read class name from .dfm/.fmx file
ClassType := GetClass(ClassName); // a reference to the class to be instantiated
NewComponent := ClassType.Create(...); // instantiate the component

The ClassType variable holds a meta class. This allows us to represent a type which is not known until runtime. We need the call to Create to be dispatched polymorphically so that the code in the component's constructor is executed. Unless you use override when declaring that constructor, it won't be.

Really, all of this boils down to polymorphism. If your understanding of polymorphism is not firm, as you suggest, then you will struggle to appreciate any of this. I think your next move is to get a better grip on what polymorphism is.

like image 106
David Heffernan Avatar answered Sep 18 '22 06:09

David Heffernan