在Delphi中使用reintroduce
关键字的动机是什么?
如果您的子类包含与父类中的虚函数同名的函数,并且未使用override修饰符声明它,则它是编译错误。在这种情况下添加reintroduce修饰符可以修复错误,但我从未理解编译错误的原因。
答案 0 :(得分:64)
如果在后代类中声明一个与祖先类中的方法同名的方法,那么您将隐藏该祖先方法 - 这意味着如果您有该后代类的实例(被引用为该类)那么你就不会得到祖先的行为。当祖先的方法是虚拟的或动态的时,编译器会给你一个警告。
现在,你有两种选择之一来压制该警告信息:
覆盖和重新引入之间的区别在于多态性。使用重新引入,如果将后代对象转换为父类型,则调用该方法将获得祖先方法,但如果您访问后代类型,则将获得后代的行为。使用覆盖,您始终可以获得后代。如果祖先方法既不是虚拟也不是动态,则重新引入不适用,因为该行为是隐式的。 (实际上你可以使用一个类助手,但我们现在不会去那里。)
尽管Malach说过,但仍然可以在重新引入的方法中调用继承的 ,即使父级既不是虚拟也不是<强>动态强>
基本上重新引入就像覆盖,但它适用于非动态和非虚拟方法,并且它如果通过祖先类型的表达式访问对象实例,则不替换行为。
进一步说明:
重新引入是一种向编译器传达意图的方法,您没有犯错误。我们使用覆盖关键字覆盖祖先的方法,但它要求祖先方法为虚拟或动态,并且您需要作为祖先类访问对象时要更改的行为。现在输入重新引入。它允许您告诉编译器您不会意外地创建一个与虚拟或动态祖先方法同名的方法(如果编译器没有警告您,这会很烦人)。
答案 1 :(得分:7)
这里有很多答案可以解释为什么一个允许你隐藏成员函数静默的编译器是个坏主意。但没有现代编译器静默隐藏成员函数。即使在C ++中,也允许这样做,总会有一个警告,这应该就足够了。
那为什么需要“重新引入”?主要原因是,当您不再查看编译器警告时,这种错误实际上可能会出现。例如,假设您继承自TComponent,Delphi设计人员为TComponent添加了一个新的虚函数。坏消息是您的派生组件,您在五年前写过并分发给其他人,已经有一个具有该名称的函数。
如果编译器刚刚接受了这种情况,某些最终用户可能会重新编译您的组件,请忽略该警告。奇怪的事情会发生,你会受到指责。这要求他们明确接受该函数不是同一个函数。
答案 2 :(得分:4)
RTL使用重新引入来隐藏继承的构造函数。例如,TComponent有一个构造函数,它接受一个参数。但是,TObject有一个无参数构造函数。 RTL希望您只使用TComponent的单参数构造函数,而不是实例化新TComponent时从TObject继承的无参数构造函数。所以它使用重新引入来隐藏继承的构造函数。通过这种方式,重新引入有点像在C#中将无参数构造函数声明为私有。
答案 3 :(得分:3)
重新引入修饰符的目的是防止出现常见的逻辑错误。
我将假设通常知道重新引入关键字如何修复警告,并解释为什么生成警告以及关键字包含在语言中的原因。考虑下面的delphi代码;
TParent = Class
Public
Procedure Procedure1(I : Integer); Virtual;
Procedure Procedure2(I : Integer);
Procedure Procedure3(I : Integer); Virtual;
End;
TChild = Class(TParent)
Public
Procedure Procedure1(I : Integer);
Procedure Procedure2(I : Integer);
Procedure Procedure3(I : Integer); Override;
Procedure Setup(I : Integer);
End;
procedure TParent.Procedure1(I: Integer);
begin
WriteLn('TParent.Procedure1');
end;
procedure TParent.Procedure2(I: Integer);
begin
WriteLn('TParent.Procedure2');
end;
procedure TChild.Procedure1(I: Integer);
begin
WriteLn('TChild.Procedure1');
end;
procedure TChild.Procedure2(I: Integer);
begin
WriteLn('TChild.Procedure2');
end;
procedure TChild.Setup(I : Integer);
begin
WriteLn('TChild.Setup');
end;
Procedure Test;
Var
Child : TChild;
Parent : TParent;
Begin
Child := TChild.Create;
Child.Procedure1(1); // outputs TChild.Procedure1
Child.Procedure2(1); // outputs TChild.Procedure2
Parent := Child;
Parent.Procedure1(1); // outputs TParent.Procedure1
Parent.Procedure2(1); // outputs TParent.Procedure2
End;
鉴于上面的代码,TParent中的两个过程都被隐藏了。说它们被隐藏意味着无法通过TChild指针调用这些过程。编译代码示例会产生一个警告;
[DCC警告] Project9.dpr(19):W1010方法'Procedure1'隐藏基类型'TParent'的虚拟方法
为什么只有虚拟功能的警告而不是其他?两者都是隐藏的。
Delphi的一个优点是库设计人员能够发布新版本而不必担心破坏现有客户端代码的逻辑。这与Java形成鲜明对比,在Java中,向库中的父类添加新函数充满了危险,因为类是隐式虚拟的。让我们说TParent从上面生活在第三方库中,图书馆制造商发布下面的新版本。
// version 2.0
TParent = Class
Public
Procedure Procedure1(I : Integer); Virtual;
Procedure Procedure2(I : Integer);
Procedure Procedure3(I : Integer); Virtual;
Procedure Setup(I : Integer); Virtual;
End;
procedure TParent.Setup(I: Integer);
begin
// important code
end;
想象一下,我们在客户端代码中有以下代码
Procedure TestClient;
Var
Child : TChild;
Begin
Child := TChild.Create;
Child.Setup;
End;
对于客户端,如果代码是针对库的版本2或版本编译的,则无关紧要,在这两种情况下都会根据用户的意图调用TChild.Setup。在图书馆;
// library version 2.0
Procedure TestLibrary(Parent : TParent);
Begin
Parent.Setup;
End;
如果使用TChild参数调用TestLibrary,则一切都按预期工作。图书馆设计师不了解TChild.Setup,在Delphi中这不会对他们造成任何伤害。上面的调用正确解析为TParent.Setup。
在Java的同等情况下会发生什么? TestClient可以按预期正常工作。 TestLibrary不会。在Java中,所有函数都假定为虚拟。 Parent.Setup将解析为TChild.Setup,但是记得在编写TChild.Setup时他们不知道未来的TParent.Setup,所以他们肯定不会调用继承的。因此,如果库设计者打算调用TParent.Setup,那么无论他们做什么都不会。当然这可能是灾难性的。
因此,Delphi中的对象模型需要在子类链的下游显式声明虚函数。这样做的副作用是很容易忘记在子方法上添加override修饰符。 Reintroduce关键字的存在对程序员来说是一种方便。 Delphi的设计是为了让程序员通过产生警告轻轻地说服他们在这种情况下明确表达他们的意图。
答案 4 :(得分:3)
首先,“重新引入”打破了继承链,应该不,我的意思是永远不会。在我用Delphi工作的整个时间里(大约10年)我偶然发现了一些使用这个关键字的地方,这在设计中一直是个错误。
考虑到这一点,这是最简单的工作方式:
就像我说这是纯粹的邪恶,必须不惜一切代价避免(好吧,至少我的意见)。这就像使用 goto - 只是一种糟糕的风格:D
答案 5 :(得分:3)
tl; dr:尝试覆盖非虚方法毫无意义。添加关键字reintroduce
以确认您犯了错误。
答案 6 :(得分:2)
当祖先类也有一个具有相同名称的方法,并且它不一定被声明为虚拟时,您会看到编译器警告(因为您将隐藏此方法)。
换句话说:你告诉编译器你知道你隐藏了祖先函数并用这个新函数替换它并故意这样做。
你为什么要这样做?如果该方法在父类中是虚拟的,唯一的原因是防止多态。其他那只是覆盖而不调用继承。但是如果父方法没有被声明为虚拟(并且你不能改变它,因为你没有代码拥有代码),你可以继承该类,让人们从你的类继承,而不会看到编译器警告。
答案 7 :(得分:2)
重新引入告诉编译器您要调用此方法中定义的代码作为此类及其后代的入口点,而不管祖先链中具有相同名称的其他方法。
创建TDescendant.MyMethod
会为TDescendants添加另一个具有相同名称的方法造成混淆,编译器会向您发出警告。
重新引入歧义并告诉编译器您知道使用哪一个
ADescendant.MyMethod
调用TDescendant,(ADescendant as TAncestor).MyMethod
调用TAncestor。总是!没有混乱......编译器开心!
无论您希望后代方法是否为虚拟方法,都是如此:在这两种情况下,您都希望打破虚拟链的自然链接。 并且它不会阻止您从新方法中调用继承的代码。
答案 8 :(得分:1)
由于Framework版本(包括VCL),已经引入了该语言。
如果您有一个现有的代码库,并且对Framework的更新(例如,因为您购买了更新的Delphi版本)引入了一个与您的代码库的祖先中的方法同名的虚方法,那么{{ 1}}将允许你摆脱W1010 warning。
这是您应该使用reintroduce
的唯一地方。
答案 9 :(得分:1)
首先,正如上面所说,你永远不应该刻意重新引入虚方法。重新引入的唯一合理用途是当祖先的作者(不是你)添加了与你的后代发生冲突的方法并且重命名你的后代方法时不是一种选择。其次,即使在使用不同参数重新引入虚拟方法的类中,也可以轻松调用虚拟方法的原始版本:
header('Location: '.$_SERVER['PHP_SELF']);
那么除了让事情更难阅读之外,重新引入虚拟方法的目的应该是什么?
答案 10 :(得分:0)
重新引入允许您声明一个与祖先同名但具有不同参数的方法。它与错误或错误无关!!!
例如,我经常将它用于构造函数......
constructor TClassname.Create (AOwner : TComponent; AParent : TComponent);
begin
inherited Create (AOwner);
Parent := AParent;
..
end;
这使我能够以更干净的方式为复杂的控件(如工具栏或日历)创建内部类。我通常有比这更多的参数。有时创建一个类而不传递一些参数几乎不可能或非常混乱。
对于可视化控件,可以在Create之后调用Application.Processmessages,这可能为时已晚,无法使用这些参数。
~~~~~~~~~~~~~~~~~~~~~~~~
| IMAGE VIEW |
| ~~~~~~~~ |
|-------|BUTTON|-------|
| ~~~~~~~~ |
| linear layout |
|______________________|