Delphi:了解构造函数
我希望理解
- 虚拟
- 重写
- 重载
- 重新引入
在应用于对象构造函数时 。每次我随机添加关键字,直到编译器关闭 - 并且(在使用 Delphi 开发 12 年之后)我宁愿知道我在做什么,而不是随机尝试。
给定一组假设的对象:
TComputer = class(TObject)
public
constructor Create(Cup: Integer); virtual;
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual;
end;
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); override;
constructor Create(Cup: Integer; Teapot: string); override;
end;
我希望它们的行为方式可能从声明中显而易见,但是:
TComputer
具有简单的构造函数,并且后代可以覆盖它TCellPhone
具有一个备用构造函数,后代可以重写它TiPhone
重写两个构造函数,调用每个构造函数的继承版本
。现在代码无法编译。我想了解为什么它不起作用。我还想了解重写构造函数的正确方法。或者也许你永远无法覆盖构造函数?或者也许重写构造函数是完全可以接受的?也许您永远不应该拥有多个构造函数,也许拥有多个构造函数是完全可以接受的。
我想了解原因。那么修复它就很明显了。
另请参阅
编辑: 我也想得到一些推理按照虚拟
、覆盖
、重载
、重新引入
的顺序。因为当尝试所有关键字组合时,组合数量会爆炸:
- virtual;超载;
- 虚拟的;覆盖;
- 覆盖;超载;
- 覆盖;虚拟的;
- 虚拟的;覆盖;超载;
- 虚拟的;超载;覆盖;
- 超载;虚拟的;覆盖;
- 覆盖;虚拟的;超载;
- 覆盖;超载;虚拟的;
- 超载;覆盖;虚拟的;
- 等等
编辑2:我想我们应该从“给定的对象层次结构是否可能?”开始,如果不可能,为什么不呢?例如,拥有来自祖先的构造函数从根本上来说是错误的吗?
TComputer = class(TObject)
public
constructor Create(Cup: Integer); virtual;
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual;
end;
我希望 TCellPhone 现在有两个构造函数。但我在 Delphi 中找不到关键字组合,使其认为这是有效的做法。我认为我可以在 TCellPhone 中拥有两个构造函数,这从根本上是错误的吗?
注意:此行下方的所有内容都不是回答问题所必需的 问题 - 但这确实有助于解释 我的想法。或许你可以看到, 根据我的思维过程,什么 我缺少的基本部分 让一切变得清晰起来。
现在这些声明无法编译:
//Method Create hides virtual method of base type TComputer:
TCellPhone = class(TComputer)
constructor Create(Cup: Integer; Teapot: string); virtual;
//Method Create hides virtual method of base type TCellPhone:
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); override;
constructor Create(Cup: Integer; Teapot: string); overload; <--------
end;
所以首先我将尝试修复TCellPhone
。我将首先随机添加 overload
关键字(我知道我不想要 reintroduce
因为这会隐藏其他构造函数,这是我不想要的):
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual; overload;
end;
但是失败:在方法或属性之后不允许进行字段定义
。
我从经验中知道,即使我在方法或属性后面没有字段,如果我颠倒 virtual
和 overload
关键字的顺序:Delphi 将关闭上:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;
但我仍然收到错误:
方法“Create”隐藏基类型“TComputer”的虚拟方法
所以我尝试删除这两个关键字:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string);
end;
但我仍然收到错误:
方法“Create”隐藏基类型“TComputer”的虚拟方法
所以我现在放弃自己尝试重新引入
:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); reintroduce;
end;
现在TCellPhone可以编译,但它使TiPhone的情况变得更糟:
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); override; <-----cannot override a static method
constructor Create(Cup: Integer; Teapot: string); override; <-----cannot override a static method
end;
两者都抱怨我不能覆盖它们,所以我删除了 override
关键字:
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer);
constructor Create(Cup: Integer; Teapot: string);
end;
但是现在第二个创建说它必须标记为重载,我这样做了(事实上,我将两者都标记为重载,因为我知道会发生什么如果我不这样做):
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); overload;
constructor Create(Cup: Integer; Teapot: string); overload;
end;
interface
部分一切都很好。不幸的是我的实现不起作用。我的TiPhone的单参数构造函数无法调用继承的构造函数:
constructor TiPhone.Create(Cup: Integer);
begin
inherited Create(Cup); <---- Not enough actual parameters
end;
i'm looking to understand
- virtual
- override
- overload
- reintroduce
when applied to object constructors. Every time i randomly add keywords until the compiler shuts up - and (after 12 years of developing with Delphi) i'd rather know what i'm doing, rather than trying things randomly.
Given a hypothetical set of objects:
TComputer = class(TObject)
public
constructor Create(Cup: Integer); virtual;
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual;
end;
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); override;
constructor Create(Cup: Integer; Teapot: string); override;
end;
The way i want them to behave is probably obvious from the declarations, but:
TComputer
has the simple constructor, and descendants can override itTCellPhone
has an alternate constructor, and descendants can override itTiPhone
overrides both constructors, calling the inherited version of each
Now that code doesn't compile. i want to understand why it doesn't work. i also want to understand the proper way to override constructors. Or perhaps you could never override constructors? Or perhaps it is perfectly acceptable to override constructors? Perhaps you should never have multiple constructors, perhaps it is perfectly acceptable to have multiple constructors.
i want to understand the why. Fixing it would then be obvious.
See also
- Delphi: How to hide ancestor constructors?
- Reintroducing functions in Delphi
- Delphi: How to add a different constructor to a descendant?
Edit: i'm also looking to get some reasoning on the order of virtual
, override
, overload
, reintroduce
. Because when trying all combinations of keywords, the number of combinations explodes:
- virtual; overload;
- virtual; override;
- override; overload;
- override; virtual;
- virtual; override; overload;
- virtual; overload; override;
- overload; virtual; override;
- override; virtual; overload;
- override; overload; virtual;
- overload; override; virtual;
- etc
Edit 2: i guess we should begin with "is the object hierarchy given even possible?" If not, why not? For example, is it fundamentally incorrect to have a constructor from an ancestor?
TComputer = class(TObject)
public
constructor Create(Cup: Integer); virtual;
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual;
end;
i would expect that TCellPhone
now has two constructors. But i can't find the combination of keywords in Delphi to make it think that's a valid thing to do. Am i fundamentally wrong in thinking i can have two constructors here in TCellPhone
?
Note: Everything below this line is not strictly needed to answer the
question - but it does help to explain
my thinking. Perhaps you can see,
based on my thought processes, what
fundamental piece i'm missing that
makes everything clear.
Now these declarations don't compile:
//Method Create hides virtual method of base type TComputer:
TCellPhone = class(TComputer)
constructor Create(Cup: Integer; Teapot: string); virtual;
//Method Create hides virtual method of base type TCellPhone:
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); override;
constructor Create(Cup: Integer; Teapot: string); overload; <--------
end;
So first i'll trying fixing TCellPhone
. i'll start by randomly adding the overload
keyword (i know i don't want reintroduce
because that would hide the other constructor, which i don't want):
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual; overload;
end;
But that fails: Field definition not allowed after methods or properties
.
i know from experience that, even though i don't have a field after a method or property, if i reverse the order of the virtual
and overload
keywords: Delphi will shut up:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;
But i still get the error:
Method 'Create' hides virtual method of base type 'TComputer'
So i try removing both keywords:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string);
end;
But i still get the error:
Method 'Create' hides virtual method of base type 'TComputer'
So i resign myself to now trying reintroduce
:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); reintroduce;
end;
And now TCellPhone compiles, but it has made things much worse for TiPhone:
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); override; <-----cannot override a static method
constructor Create(Cup: Integer; Teapot: string); override; <-----cannot override a static method
end;
Both are complaining that i cannot override them, so i remove the override
keyword:
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer);
constructor Create(Cup: Integer; Teapot: string);
end;
But now the 2nd create says it must be marked with overload, which i do (in fact i'll mark both as overload, since i know what will happen if i don't):
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); overload;
constructor Create(Cup: Integer; Teapot: string); overload;
end;
All all is good in the interface
section. Unfortunately my implementations won't work. My single parameter constructor of TiPhone cannot call the inherited constructor:
constructor TiPhone.Create(Cup: Integer);
begin
inherited Create(Cup); <---- Not enough actual parameters
end;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我发现您的原始声明集不能干净地编译有两个原因:
TCellPhone 中应该有一个警告,其构造函数隐藏基类的方法。这是因为基类方法是虚拟的,并且编译器担心您引入了同名的新方法而不覆盖基类方法。签名不同并不重要。如果您的意图确实是隐藏基类的方法,那么您需要在后代声明上使用
reintroduce
,正如您的盲目猜测之一所示。该指令的唯一目的是消除警告;它对运行时行为没有影响。忽略稍后
TIPhone
将会发生的情况,以下TCellPhone
声明就是您想要的。它隐藏了祖先方法,但您也希望它是虚拟的。它不会继承祖先方法的虚拟性,因为它们是两个完全独立的方法,只是碰巧具有相同的名称。因此,您还需要在新声明上使用virtual
。基类构造函数
TComputer.Create
也隐藏了其祖先TObject.Create
的方法,但由于TObject
中的方法不是虚拟的,编译器不会对此发出警告。隐藏非虚拟方法时常发生,并且通常不引人注目。您应该在
TIPhone
中收到错误,因为不再有任何需要重写的单参数构造函数。您将其隐藏在TCellPhone
中。由于您想要有两个构造函数,因此reintroduce
显然不是之前使用的正确选择。您不想隐藏基类构造函数;你想用另一个构造函数来增强它。由于您希望两个构造函数具有相同的名称,因此需要使用
overload
指令。该指令需要在所有原始声明中使用 - 第一次引入每个不同的签名后代中的后续声明。我认为所有声明(甚至是基类)都需要它,这样做并没有什么坏处,但我想这不是必需的。因此,您的声明应如下所示:现代文档 告诉一切内容应按什么顺序进行:
这是六个不同的类别,但根据我的经验,任何声明中很少有超过三个的类别。 (例如,需要指定调用约定的函数可能不是方法,因此它们不能是虚拟的。)我从来不记得顺序;直到今天我才看到它的记录。相反,我认为记住每个指令的目的更有帮助。当您记住不同任务需要哪些指令时,您最终只会得到两到三个指令,然后就可以非常简单地进行试验以获得有效的指令。编译器可能接受多个顺序,但不用担心 - 顺序对于确定含义并不重要。编译器接受的任何顺序都将具有与其他任何顺序相同的含义(调用约定除外;如果您提到其中多个顺序,则只有最后一个有效,所以不要这样做)。
因此,您只需记住每个指令的目的,并考虑哪些指令放在一起没有任何意义。例如,您不能同时使用
reintroduce
和override
,因为它们具有相反的含义。而且您不能同时使用virtual
和override
,因为其中一个暗示着另一个。如果您堆积了很多指令,那么您始终可以在计算出所需的其余指令时将
overload
从图片中剔除。为您的方法指定不同的名称,找出它们自己需要哪些其他指令,然后在再次为它们提供相同的名称时添加overload
。I see two reasons your original set of declarations shouldn't compile cleanly:
There should be a warning in
TCellPhone
that its constructor hides the method of the base class. This is because the base-class method is virtual, and the compiler worries that you're introducing a new method with the same name without overriding the base-class method. It doesn't matter that the signatures differ. If your intention is indeed to hide the method of the base class, then you need to usereintroduce
on the descendant declaration, as one of your blind guesses showed. The sole purpose of that directive is to quell the warning; it has no effect on run-time behavior.Ignoring what's going to happen with
TIPhone
later on, the followingTCellPhone
declaration is what you'd want. It hides the ancestor method, but you want it to be virtual as well. It won't inherit the virtualness of the ancestor method because they're two completely separate methods that just happen to have the same name. Therefore, you need to usevirtual
on the new declaration as well.The base-class constructor,
TComputer.Create
, is also hiding a method of its ancestor,TObject.Create
, but since the method inTObject
is not virtual, the compiler doesn't warn about it. Hiding non-virtual methods happens all the time and is generally unremarkable.You should get an error in
TIPhone
because there is no longer any one-argument constructor to override. You hid it inTCellPhone
. Since you want to have two constructors,reintroduce
clearly wasn't the right choice to use earlier. You don't want to hide the base-class constructor; you want to augment it with another constructor.Since you want both constructors to have the same name, you need to use the
overload
directive. That directive needs to be used onall the original declarations — the first time each distinct signature is introducedsubsequent declarations in descendants. I thought it was required on all declarations (even the base class), and it doesn't hurt to do that, but I guess it's not required. So, your declarations should look like this:Modern documentation tells what order everything should go in:
Those are six different categories, but in my experience, it's rare to have more than three on any declaration. (For example, functions that need calling conventions specified probably aren't methods, so they can't be virtual.) I never remember the order; I've never seen it documented till today. Instead, I think it's more helpful to remember each directive's purpose. When you remember which directives you need for different tasks, you'll end up with just two or three, and then it's pretty simple to experiment to get a valid order. The compiler might accept multiple orders, but don't worry — order isn't important in determining meaning. Any ordering the compiler accepts will have the same meaning as any other (except for calling conventions; if you mention more than one of those, only the last one counts, so don't do that).
So, then you just have to remember the purpose of each directive, and think about which ones don't make any sense together. For example, you cannot use
reintroduce
andoverride
at the same time because they have opposite meanings. And you can't usevirtual
andoverride
together because one implies the other.If you have lots of directives piling up, you can always cut
overload
out of the picture while you work out the rest of the directives you need. Give your methods different names, figure out which of the other directives they need by themselves, and then addoverload
back while you give them all the same names again.请注意,我没有 Delphi 5,因此我的答案基于最新版本 Delphi XE。我认为这不会真正产生任何影响,但如果确实如此,我们已经警告过您。 :)
这主要基于 http://docwiki.embarcadero.com/RADStudio/en/Methods< /a>,这是方法如何工作的当前文档。您的 Delphi 5 帮助文件可能也有与此类似的内容。
首先,虚拟构造函数在这里可能没有多大意义。在某些情况下您确实需要这样做,但这可能不是一种。看看http://docwiki.embarcadero.com/RADStudio/en/Class_References对于确实需要虚拟构造函数的情况 - 如果您在编码时始终知道对象的类型,但是,您并不知道。
然后,您在 1 参数构造函数中遇到的问题是您的父类本身没有 1 参数构造函数 - 继承的构造函数不会公开。您不能使用
inherited
在层次结构中向上移动多个级别,您只能调用您的直接父级。您将需要使用一些默认值调用 2 参数构造函数,或者也向 TCellPhone 添加 1 参数构造函数。一般来说,这四个关键字具有以下含义:
virtual
- 将其标记为您需要运行时调度的函数(允许多态行为)。这仅适用于初始定义,不适用于子类中的重写。override
- 为虚拟方法提供新的实现。overload
- 将一个函数标记为与另一个函数同名,但参数列表不同。reintroduce
- 告诉编译器您实际上打算隐藏虚拟方法,而不是仅仅忘记提供覆盖
。文档中详细说明了所需的订购:
Note that I don't have Delphi 5, so I'm basing my answers off the newest version, Delphi XE. I don't think that will really make any difference here, but if it does, you've been warned. :)
This is mostly based on http://docwiki.embarcadero.com/RADStudio/en/Methods, which is the current documentation of how methods work. Your Delphi 5 help file probably has something similar to this as well.
First off, a virtual constructor may not make much sense here. There are a few cases where you do want this, but this probably isn't one. Take a look at http://docwiki.embarcadero.com/RADStudio/en/Class_References for a situtation where you do need a virtual constructor - if you always know the type of your objects when coding, however, you don't.
The problem you then get in your 1-parameter constructor is that your parent class doesn't have a 1-parameter constructor itself - inherited constructors are not exposed. You can't use
inherited
to go up multiple levels in the hierarchy, you can only call your immediate parent. You will need to call the 2-parameter constructor with some default value, or add a 1-parameter constructor to TCellPhone as well.In general, the four keywords have the following meanings:
virtual
- Mark this as a function where you will want run-time dispatching (allows polymorphic behavior). This is only for the initial definition, not when overriding in subclasses.override
- Provide a new implementation for a virtual method.overload
- Mark a function with the same name as another function, but a different parameter list.reintroduce
- Tell the compiler you actually intended to hide a virtual method, instead of merely forgetting to supplyoverride
.The ordering required is detailed in the documentation:
这是所需定义的有效实现:
结果:
第一部分符合 这个。
TiPhone
两个构造函数的定义如下:TCellPhone
一个,同时覆盖另一个构造函数。This is a working implementation of the definitions wanted:
with results:
The first part is in accordance with this. The definition of the
TiPhone
two constructors then proceeds as follows:overload; override
to overload theTCellPhone
one while overriding the other constructor.override
to override its sibling.对两者都使用重载,这就是我的做法,并且有效。
构造函数创建;重载
; <-- 此处使用重载构造函数值; Overload; <--这里
记住不要对两个不同的构造函数使用相同的名称
use overload on both, it's the way i do it, and it works.
constructor Create; Overload
; <-- use overload hereconstructor Values; Overload;
<-- and hereremember not to use the same name for two different constructors