use*_*190 3 delphi inheritance constructor
如何指定在Delphi中调用哪个基类的重写方法?
比方说,像这样的继承行:TObject - > ... SomeMoreBaseClass ... - > ParentClass - > MyClass
假设ParentClass没有Create(),但它有一个Create(int = 0).这样当你调用ParentClass.Create()时,它实际上调用ParentClass.Create(0)
现在,在MyClass的构造函数Create()中,如果我调用"inherited;",我发现我没有得到ParentClass.Create(0),而是得到基类的.Create()甚至是TObject.
那么,我怎样才能调用ParentClass.Create()?
最简单的是"继承Create(0)",但它感觉不够"正确".
(在我的情况下,ParentClass实际上是System.Generics.Collections.TDictionary)
type
TParentClass = class
public
constructor Create(n:Integer = 0);
end;
TDerivedClass = class(TParentClass)
public
constructor Create; // Note: no parameters
end;
constructor TDerivedClass.Create;
begin
// inherited; // this calls TObject.Create, not TParentClass.Create(0);
inherited Create(0);
end;
Run Code Online (Sandbox Code Playgroud)
Dav*_*nan 12
首先,正如@Cosmin详细解释的那样,这个问题并不涉及被覆盖的方法.问题是关于调用继承的方法.
inherited Create;
Run Code Online (Sandbox Code Playgroud)
是你在这里做的最好的.这将调用TDictionary<TKey,TValue>构造函数传递默认ACapacity的0.
实际上甚至可能更喜欢写:
inherited Create(0);
Run Code Online (Sandbox Code Playgroud)
并且非常明确.
我的假设是您的代码如下所示:
type
TMyClass<TKey,TValue> = class(TDictionary<TKey,TValue>)
public
constructor Create;
end;
constructor TMyClass<K,V>.Create;
begin
inherited;
end;
Run Code Online (Sandbox Code Playgroud)
我读的文档,企图了解的差额继承的关键字inherited和inherited Create.最佳线索包含在以下摘录中:
如果继承后跟成员名称,则表示正常的方法调用...
和
当继承后面没有标识符时,它引用与封闭方法同名的继承方法.在这种情况下,inherited不使用显式参数,而是将与调用封闭方法相同的参数传递给inherited方法.
这似乎暗示了两种竞争用途的inherited区别对待.
我的理解是inherited导致调用具有匹配参数的构造函数.在你的情况下,TMyClass<K,V>.Create是无参数的,所以唯一匹配的构造函数是TObject.请注意,没有任何构造函数TDictionary可以匹配,因为它们都接受参数.
另一方面,当你写inherited Create这是一个正常的方法调用.因此可以将默认参数添加到方法调用中.关键的一点是,此变体允许使用不匹配的参数列表调用继承的方法.
在我看来,inherited应该为虚方法保留没有以下标识符的语法.
设计师TDictionary<TKey,TValue>可以让你摆脱这种不幸的命运.TDictionary<TKey,TValue>应该像这样实现构造函数:
constructor Create; overload;
constructor Create(ACapacity: Integer); overload;
.....other constructors omitted
Run Code Online (Sandbox Code Playgroud)
那么无参数构造函数的实现就是:
constructor TDictionary<TKey,TValue>.Create;
begin
Create(0);
end;
Run Code Online (Sandbox Code Playgroud)
如果做出这个决定,那么声明的无参数构造函数TObject将从任何派生类中隐藏,并且您的代码将按预期工作.
您在这里遇到的问题是涉及重载,默认参数,无参数构造函数TObject和构造函数的古怪语法的事件的不愉快汇合的结果inherited.虽然写作inherited具有高度的可读性和简洁性,但当重载方法发挥作用时,它只会导致混淆.
首先,这是一个不好的例子:TDictionary.Create不是虚拟构造函数,所以你实际上并没有覆盖它.你只是在新课程中重新介绍它.这实际上是一件好事,因为您可以使用技巧从您想要的任何基类调用非虚方法.你可以简单地使用这样的东西:
TBaseClass(Self).NonVirtualMethodName(Parameters).
Run Code Online (Sandbox Code Playgroud)
或者在你的情况下:
constructor TMyDerived.Create;
begin
TDictionary<T>(Self).Create; // cast and call the constructor you want.
end;
Run Code Online (Sandbox Code Playgroud)
构造函数可以作为普通方法调用,Delphi允许这样设计.尽管如此,构造函数是特殊方法.即使你可以使用强制转换技巧来调用你想要的任何构造函数,你也不应该这样做:它"破坏OOP":如果你的直接父亲依赖于它自己的构造函数中发生的事情会怎么样?你不应该知道或关心祖先类在构造函数中做了什么.
我提到TDictionary.Create的不是虚拟构造函数,这就是原因.调用虚拟和非虚拟方法的方式存在根本区别.通过"虚方法表"调用虚方法,您将始终获得实际实例化的对象的方法.非虚方法在编译时解析.在下面的例子中,你会发现两者X并Y使用相同的对象类被实例化TSecondChild,但在调用的时候NonVirtual方法的结果取决于变量的类型是不同的.不是这样VMethod,这是一个虚方法,总是调用正确的方法.
这对虚拟构造函数有影响,因为您正在讨论构造函数.例如,如果你做这样的事情,你最终会得到一个无限的递归循环:
constructor TDemoClass.Create; // where Create is VIRTUAL, not the case with TDictionary
begin
// I'm "smart", I don't call Inherited
TBaseClass(Self).Create; // Ooops, I just called myself! Recursive loop!
end;
Run Code Online (Sandbox Code Playgroud)
这是演示控制台应用程序,它演示了虚拟和非虚拟方法之间的区别以及如何调用继承的方法:
program Project13;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TBase = class
public
procedure NonVirtual;
procedure VMethod(N:Integer);virtual;
end;
TFirstChild = class(TBase)
public
procedure NonVirtual;
procedure VMethod(N:Integer);override;
end;
TSecondChild = class(TFirstChild)
public
procedure NonVirtual;
procedure VMethod(N:Integer);override;
end;
{ TBase }
procedure TBase.NonVirtual;
begin
WriteLn('TBase.NonVirtual');
end;
procedure TBase.VMethod(N:Integer);
begin
WriteLn('TBase.VMethod');
end;
{ TFirstChild }
procedure TFirstChild.NonVirtual;
begin
WriteLn('TFirstChild.NonVirtual');
end;
procedure TFirstChild.VMethod(N:Integer);
begin
WriteLn('TFirstChild.VMethod');
end;
{ TSecondChild }
procedure TSecondChild.NonVirtual;
begin
WriteLn('TSecondChild.NonVirtual');
TBase(Self).NonVirtual;
end;
procedure TSecondChild.VMethod(N:Integer);
begin
WriteLn('TSecondCHild.VMethod, N=', N);
if N > 0 then // This stops infinite recursion
TBase(Self).VMethod(N-1);
end;
var X: TFirstChild;
Y: TSecondChild;
begin
try
WriteLn('Calling through a variable of type TFirstChild');
X := TSecondChild.Create;
X.NonVirtual; // Writes TFirstChild.NonVirtual
X.VMethod(2); // Writes TSecondChild.NonVirtual, 3 times
WriteLn;
WriteLn('Calling through a variable of type TSecondChild');
Y := TSecondChild.Create;
Y.NonVirtual; // Writes TSecondChild.NonVirtual
Y.VMethod(2); // Writes TSecondChild.NonVirtual, 3 times
WriteLn;
WriteLn('Press ENTER');
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Run Code Online (Sandbox Code Playgroud)