错误调用继承会造成什么影响?

3
在查找 Delphi XE 程序高度间歇性的内存损坏时,我发现一个类构造函数在初始化了类中的一些字段后才调用 inherited。我认为这些初始化是在构造函数编写之后添加的,并且不小心放错了位置。我已经更正为先调用 inherited。这个类的方法几乎总是会出现内存损坏的异常。
问题:这个错误是否可能导致间歇性的内存损坏?在跟踪代码时,似乎并不是这样,但我真的希望这个修复可以解决间歇性问题。修复问题后一段时间不再出现并不能证明它已经消失。
一些代码:
Tmyclass = class
  ctype : integer;
  ts : tstringlist;
  th : thandle;
public
  Constructor Create;
  Destructor Destroy; override;
  ...
end;

Constructor Tmyclass.Create;
begin
  ctype := 3;
  doinit;
  inherited;
end;

4
给我们展示一些代码。可能会出现各种问题,也可能没有。 - David Heffernan
3个回答

5
以下是对象创建的典型步骤:
  • 分配对象实例的内存;
  • 用零填充所有内存(以初始化所有字段,特别是字符串);
  • 调用所有嵌套构造函数,从最后一个子级开始,让inherited调用每个父级 - 这就是为什么你应该在两个构造函数和析构函数中写inherited
因此,inherited调用父方法 - 甚至可以指定父级别,或者如果您确信可以这样做,则不调用任何方法(但可能会破坏SOLID原则)。
事实上,在调用constructor时,方法中会添加一个隐藏参数:
构造函数和析构函数使用与其他方法相同的调用约定,只是会传递一个额外的布尔标志参数来指示构造函数或析构函数调用的上下文。
在构造函数调用的标志参数中为False表示该构造函数是通过实例对象或使用inherited关键字调用的。在这种情况下,构造函数的行为类似于普通方法。在构造函数调用的标志参数中为True表示该构造函数是通过类引用调用的。在这种情况下,构造函数创建Self指定的类的实例,并在EAX中返回对新创建对象的引用。
在析构函数调用的标志参数中为False表示该析构函数是使用inherited关键字调用的。在这种情况下,析构函数的行为类似于普通方法。在析构函数调用的标志参数中为True表示该析构函数是通过实例对象调用的。在这种情况下,析构函数在返回之前释放Self指定的实例。
标志参数的行为就好像它是在所有其他参数之前声明的一样。在寄存器约定下,它通过DL寄存器传递。在Pascal约定下,它在所有其他参数之前推送。在cdecl、stdcall和safecall约定下,它在Self参数之前推送。

来源: Delphi官方文档

因此,无论何时调用inherited,都可以确保其安全处理。例如,字段的初始化(重置为0)只会在所有构造函数被调用之前处理一次。

TObject.Create默认构造函数(在您的inherited行中调用的函数)只是一个begin end空语句块,什么也不做。这甚至不是必须调用inherited,但这是一个好习惯,因为如果更改对象层次结构,可能仍然需要调用它。

唯一的问题可能是,某些字段在子类中设置后在inherited方法中进行了设置(例如,ctype := 2),但这不是编译器的错,而是由用户代码决定!


1

在调用继承的构造函数之前初始化一些字段并不一定是一个错误。有时候,继承的构造函数会调用一些被后代重写的虚拟方法,而这些新的实现依赖于这些字段被正确地初始化。

(我并不是说这是好的设计,但它不是一个错误。)


0
在Delphi中,您可以在调用继承的构造函数之前初始化对象字段(这在Turbo Pascal或“旧”对象模型中不起作用,但在Delphi“新”对象模型中是允许的)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接