如果没有调用TPngImage.Free(),当TPngImage超出作用域时,TPngImage.Destroy()是否会被调用?

3

我有一个以TPicture为参数并返回TPngImage的函数。为了保存原始图像,我创建了一个TPngImage并将TPicture复制到TPngImage中,应用效果并返回TPngImage。就像这样。

function Effect( const Value : TPicture ) : TPngImage;
  var
    AnImage : TPngImage;

  begin
    if( Value.Graphic is TPngImage ) then
      begin
        AnImage := TPngImage.Create();
        AnImage.Assign( TPngImage( Value ) );
        //Apply effect
        Result := AnImage;
        //AnImage.Free(); //error
      end;
  end;

procedure TForm11.Button1Click( Sender : TObject );
  begin
    Image2.Picture.Assign( Effect( Image1.Picture ) );
  end;

当我创建一个对象时,何时释放所创建的对象?我不能在函数中调用TPngImage.Free(),因为这会在分配之前销毁对象。那么我该如何释放所创建的对象?当对象超出范围时,TPngImage是否调用其析构函数?据我所知,不释放对象会导致内存泄漏。


1
在手动内存管理(例如 Delphi)中,最好根本不要返回对象。传递一个由您管理生命周期的对象,让例程简单地使用该对象即可。除非您的平台使用 ARC,否则不会自动释放任何内容。 - Rudy Velthuis
1
Result被赋予了AnImage所持有的引用。两者都成为指向同一PNG图像对象的引用。AnImage.Free释放该对象。实际上,这相当于编写Result.Free。 - Sertac Akyuz
1
这里有两个错误。首先,你泄漏了内存。这很容易解决。在使用完对象后销毁它。另一个错误是Effect函数并不总是分配返回值。 - David Heffernan
1
你设计的方式不会按照你的期望工作。你需要确保在那个函数之外释放它。我不明白为什么你要保留原始图像,特别是当你将其分配给图像控件后最终会丢弃它。我建议放弃返回副本的想法,改为传递对象并直接对其执行效果的过程。 - Jerry Dodge
1
@GrooverMD,Windows的任务管理器无法显示您的应用程序的真实内存消耗。请使用专门的工具来解决此问题。例如FastMM(适用于此类目的的免费工具之一)。 - Josef Švejk
显示剩余9条评论
2个回答

5

你的代码有几个bug:

if( Value.Graphic is TPngImage ) then

如果调用者的 TPicture 中没有包含 TPNGImage,则您根本不返回任何内容。 Result 是未定义的。

实际上,您根本不应该检查 Graphic 的类型。各种 TGraphic 类可以相互赋值,从一个格式转换为另一种格式,因此应尽可能让此转换发生。

AnImage.Assign( TPngImage( Value ) );
您正在对TPicture本身进行类型转换。您需要对其Graphic进行类型转换。
Result := AnImage;
//AnImage.Free();
这要求调用者接管 TPNGImage 并释放它,这通常是一个不好的设计。
Image2.Picture.Assign( Effect( Image1.Picture ) );

比如说,调用者没有对返回的 TPngImage 进行所有权控制,导致它泄漏了。

如果你想要返回一个新的 TPNGImage,请尝试下面这种方式:

function Effect(Value : TPicture) : TPngImage;
begin
  Result := TPngImage.Create;
  try
    if (Value.Graphic <> nil) and (not Value.Graphic.Empty) then
    begin
      Result.Assign(Value.Graphic);
      //Apply effect
    end;
  except
    Result.Free;
    raise;
  end;
end;

或者

function Effect(Value : TPicture) : TPngImage;
begin
  Result := nil;
  if (Value.Graphic <> nil) and (not Value.Graphic.Empty) then
  begin
    Result := TPngImage.Create;
    try
      Result.Assign(Value.Graphic);
      //Apply effect
    except
      Result.Free;
      raise;
    end;
  end;
end;

无论哪种方式,你都可以这样做:
procedure TForm11.Button1Click(Sender : TObject);
var
  AImage: TPngImage;
begin
  AImage := Effect(Image1.Picture);
  try
    Image2.Picture.Assign(AImage);
  finally
    AImage.Free;
  end;
end;

然而,更好的设计是根本不返回新的TPngImage。传入2个TPicture对象,让Effect()根据需要进行操作:

procedure Effect(Input, Output : TPicture);
var
  AnImage : TPngImage;
begin
  AnImage := TPngImage.Create;
  try
    if (Input.Graphic <> nil) and (not Input.Graphic.Empty) then
    begin
      AnImage.Assign(Input.Graphic);
      //Apply effect
    end;
    Output.Assign(AnImage);
  finally
    AnImage.Free;
  end;
end;

或者

procedure Effect(Input, Output : TPicture);
var
  AnImage : TPngImage;
begin
  if (Input.Graphic <> nil) and (not Input.Graphic.Empty) then
  begin
    AnImage := TPngImage.Create.Create;
    try
      AnImage.Assign(Input.Graphic);
      //Apply effect
      Output.Assign(AnImage);
    finally
      AnImage.Free;
    end;
  end else
    Output.Assign(nil);
end;

然后你可以这样做:
procedure TForm11.Button1Click(Sender : TObject);
begin
  Effect(Image1.Picture, Image2.Picture);
end;

这就是为什么你是Remy Lebeau。我感谢你给出的答案。你帮助有编程问题的人而不是打压他们,不像一些软件工程师(如David)认为我们和他一样聪明。 - GrooverMD

0

你现在的做法会导致内存泄漏。

你可以像这样从函数中返回一个对象,但使用后(在这种情况下,当你把它赋值给Image2.Picture后),你必须Free它。但只有你拥有对它的引用时才能这样做。所以请按照以下步骤操作:

procedure TForm11.Button1Click(Sender: TObject);
var
  EffectImage: TPngImage;
begin
  EffectImage := Effect(Image1.Picture);
  Image2.Picture.Assign(EffectImage);
  EffectImage.Free;
end;

请注意,如果图像不是PNG格式,您的Effect函数将不会返回任何有效内容。这也是您需要解决的问题。
或者,我会这样做:
procedure ProcEffect(InPicture, OutPicture: TPicture);
var
  AnImage : TPngImage;
begin
  if InPicture.Graphic is TPngImage then
  begin
    AnImage := TPngImage.Create;
    try
      AnImage.Assign(InPicture);
      //Apply effect
      OutPicture.Graphic := AnImage; // same as OutPicture.Assign(AnImage);
    finally
      AnImage.Free;
    end;
  end;
end;

然后使用以下代码进行调用:

procedure TForm11.Button1Click(Sender: TObject);
begin
  ProcEffect(Image1.Picture, Image2.Picture);
end;    

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