Delphi在构造函数中引发异常

Delphi在构造函数中引发异常,delphi,Delphi,形势 我要写一个类,构造函数是一个自定义的,因为我需要初始化一些值。这是我迄今为止编写的代码: type TCombinatorio = class(TObject) private valN, valK: integer; result: double; public property K: integer read valK; property N: integer read valN; constructor Create(valN: integer

形势

我要写一个类,构造函数是一个自定义的,因为我需要初始化一些值。这是我迄今为止编写的代码:

type
 TCombinatorio = class(TObject)
  private
   valN, valK: integer;
   result: double;
  public
   property K: integer read valK;
   property N: integer read valN;
   constructor Create(valN: integer; valK: integer);
 end;

constructor TCombinatorio.Create(valN: Integer; valK: Integer);
begin
  inherited Create;
   Self.valN := valN;
   Self.valK := valK;

  if ((valN < 0) or (valK < 0)) then
   begin
    raise Exception.Create('N and K must be >= 0');
   end;

end;
正如你在这里看到的,我的构造函数的参数是错误的,因为第二个是负数。我也无法理解(根据我的构造函数的代码)finally中是否真的需要
a.Free
,因为当构造函数引发异常时,会调用析构函数


我想包括
a:=TCombinatorio.Create(5,-2)在try finally块中以避免问题,但我不确定。你觉得怎么样?

你的代码绝对正确。从构造函数中引发异常是完全值得尊敬的。正如您所知,析构函数被调用

您询问此代码:

a := TCombinatorio.Create(5,-2);
try
  //some code
finally
  a.Free; 
end;
您担心对象已销毁后会调用
Free
。这不可能发生。如果在构造函数中引发异常,则它会向上传播调用堆栈。这发生在
try
块开始之前,因此
finally
块不会执行。实际上,
a
的赋值没有发生

try
中移动创建将是灾难性的,事实上这是一个极其常见的错误。假设你这样做了:

// WARNING THIS CODE IS DEFECTIVE 
try
  a := TCombinatorio.Create(5,-2);
  //some code
finally
  a.Free; 
end;

现在,如果引发异常,则调用
Free
,但调用什么?变量
a
未初始化。即使它是,但它不是,这仍然是一个双重免费

好的,首先您可以在构造函数中引发异常,是的,它会调用析构函数作为结果。您显示的代码很好。但我认为您误解了代码的作用。将构造函数放在try-finally块中是错误的。我认为您缺少的一点是,如果您的构造函数失败,
try…finally
块永远不会执行,因此free不会执行。如果构造函数没有成功,您不应该调用free,这就是为什么您不应该将构造函数放在
try…finally
块中。

首先,我要说的是,您无法避免构造函数中的异常,因此它不能成为反模式。若您检查Delphi源代码,您将发现在构造函数中引发异常的位置的数量。比如说

constructor TCustomForm.Create(AOwner: TComponent);
begin
  // ... skipped some lines
        if not InitInheritedComponent(Self, TForm) then
          raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);
您应该知道的唯一一件事是,如果构造函数中出现异常,Delphi将自动调用析构函数。实际上,这意味着您的析构函数可以在部分构造的对象上执行,正确编写析构函数是您的责任。请参阅文档,并特别注意以下报价:

注意:如果构造函数中逃逸了异常,则调用析构函数来销毁已生成的部分构造的对象实例 未能完全初始化。因此,析构函数应该检查 已分配的资源(如句柄)实际上已分配 在尝试释放它们之前,因为它们的值可能为零


PS通常,您应该假设每行代码都可能引发异常,但请不要妄想症;)

@kludg-如果您在构建时知道某个实例将不可用,则构造函数是引发异常的最佳位置。另一种方法是在实例被使用时引发异常,这可能导致有趣的调试会话,以了解错误的来源。也就是说,在这种特殊情况下,OP应该转换为无符号整数。@kludg:我完全不同意。请注意,与局部变量不同,引用类型的类字段(如
TObject
)总是使用
nil
初始化。在析构函数中调用
myAggregatedObject.Free()
,一切正常。但是,您应该避免在构造函数中调用虚方法,但这是完全不同的情况,不仅适用于Delphi。@serg在析构函数中引发异常是错误的。但它是为构造函数显式设计和支持的。“你写析构函数不是为了处理部分构造吗?”LievenKeersmaekers。该类可以处理负数。它说他们是不被允许的。如果您在尝试使用正常整数创建时(不可避免地会这样做),使其构造函数仅处理无符号的,则需要检查该数字是否为负数。默认情况下,编译器只能在警告方面提供帮助,而您无法在编译时知道整数的值。它可能总是有效的。可能不会。建议的方法的唯一替代方法是将测试放在创建对象的每个代码实例之外,从现在到永远。@Dsm-如果启用了范围检查错误(我相信您应该这样做),则尝试传递负值时会出现错误。自己测试和提出错误只是开销。如果一个人编译他的程序时没有范围检查错误,那么所有的赌注都没有了,但在这种情况下,很可能程序中还有很多其他微妙的错误等待被发现。我想补充一点,如果你这样做(在ctor中引发异常),在客户端应用程序中的每次调用之前,您必须测试对象是否已创建!因为如果发生异常-->自动调用析构函数-->对象=nil-->
对象。DoSomething
将导致访问冲突通常不需要测试它,因为您的
对象。DoSomething
代码不应因异常而执行。如果您能在需要此检查的地方发布代码示例,我将不胜感激。我假设您已经手动处理了构造函数异常,或者可以从析构函数调用代码。
constructor TCustomForm.Create(AOwner: TComponent);
begin
  // ... skipped some lines
        if not InitInheritedComponent(Self, TForm) then
          raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);