Delphi 在构造函数中引发异常

t0ybt7op  于 2022-11-23  发布在  其他
关注(0)|答案(4)|浏览(222)

情况

我将写一个类,构造函数是我自己定制的,因为我需要初始化一些值。这是我目前为止写的代码:

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;

因为我要做一些数学计算,我需要避免负数。

问题

我可以用这种方式在构造函数中引发异常吗?我是用这种方式运行代码的:

procedure TForm1.Button1Click(Sender: TObject);
var a: TCombinatorio; 
    b: string;   
begin

 a := TCombinatorio.Create(5,-2);

 try
  //some code
 finally
  a.Free; 
 end;

end;

正如你在这里看到的,我的构造函数有错误的参数,因为第二个参数是负的。我也不能理解(根据我的构造函数的代码)finally中的a.Free是否真的需要,因为当构造函数引发异常时,析构函数被调用。
我想在try-finally块中包含a := TCombinatorio.Create(5,-2);来避免这个问题,但我不确定。你怎么想?

hmtdttj4

hmtdttj41#

你的代码是绝对正确的。从构造函数中引发异常是非常值得尊敬的。正如你所知道的,析构函数被调用了。
您询问以下代码:

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没有被初始化。即使它被初始化了,它也没有被初始化,这仍然是一个double free。

j9per5c4

j9per5c42#

首先可以在构造函数中引发异常,是的,它确实调用了析构函数。你展示的代码是好的。但是我认为你误解了你的代码的作用。把构造函数放在try finally块中是错误的。我认为你忽略了一点,如果你的构造函数失败,try...finally块永远不会被执行,所以free也不会被执行。如果构造函数不成功,就不应该调用free,这就是为什么不应该将构造函数放在try...finally块中。

rsaldnfx

rsaldnfx3#

首先我要说的是,你不能避免构造函数中的异常,所以它不可能是一个反模式。如果你检查 Delphi 源代码,你会发现构造函数中出现异常的地方很多。例如

constructor TCustomForm.Create(AOwner: TComponent);
begin
  // ... skipped some lines
        if not InitInheritedComponent(Self, TForm) then
          raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);

你唯一需要知道的是,如果异常从构造函数中逸出, Delphi 会自动调用 * 析构函数 *。实际上,这意味着你的 * 析构函数 * 可能会在一个部分构造的对象上执行,正确编写 * 析构函数 * 是你的责任。请参阅TObject.Destroy文档,并特别注意下面的引文:

**注意:**如果例外状况逸出建构函式,则会呼叫解构函式来终结无法完全初始化的部分建构对象执行严修。因此,解构函式应该在尝试释放配置资源(例如控制代码)之前,先检查它们是否确实配置,因为它们的值可能是零。

PS一般来说,你应该假设每一行代码都可能引发一个异常,但是请不要偏执;)

tkclm6bt

tkclm6bt4#

在这种情况下,我通常会添加检查数据的方法:

.. = class
function DataValid : boolean;
...
end;

诸多优势:
1.构造函数中没有异常。它只是简单地将其参数复制到类内字段。
1.不需要部分创建类的特殊析构函数。
1.代码简单性。

相关问题