为什么这个程序会报告内存泄漏?
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create;
end;
constructor TDerivedGenericObjectList.Create;
begin
inherited;
end;
var
List: TDerivedGenericObjectList;
begin
ReportMemoryLeaksOnShutdown := True;
List := TDerivedGenericObjectList.Create;
List.Add(TObject.Create);
List.Free;
end.
你正在调用无参数构造函数 TObjectList<T>
。这实际上是构造函数 TList<T>
,从哪个班级 TObjectList<T>
是派生的。
声明的所有构造函数 TObjectList<T>
接受一个名为的参数 AOwnsObjects
用来初始化 OwnsObjects
属性。因为你绕过了那个构造函数, OwnsObjects
默认为 False
,并且列表的成员没有被销毁。
你应该确保你调用的构造函数 TObjectList<T>
初始化 OwnsObjects
。例如:
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create;
end;
constructor TDerivedGenericObjectList.Create;
begin
inherited Create(True);
end;
var
List: TDerivedGenericObjectList;
begin
ReportMemoryLeaksOnShutdown := True;
List := TDerivedGenericObjectList.Create;
List.Add(TObject.Create);
List.Free;
end.
也许更好的变体是让你的构造函数也提供 AOwnsObjects
参数:
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create(AOwnsObjects: Boolean = True);
end;
constructor TDerivedGenericObjectList.Create(AOwnsObjects: Boolean);
begin
inherited Create(AOwnsObjects);
end;
要么:
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create(AOwnsObjects: Boolean = True);
end;
constructor TDerivedGenericObjectList.Create(AOwnsObjects: Boolean);
begin
inherited;
end;
所以,你可能想知道为什么原版本选择了一个 TList<T>
构造函数而不是一个 TObjectList<T>
。好吧,让我们更详细地看一下。这是你的代码:
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create;
end;
constructor TDerivedGenericObjectList.Create;
begin
inherited;
end;
什么时候 inherited
以这种方式使用,编译器查找具有与此签名完全相同的签名的构造函数。它找不到一个 TObjectList<T>
因为他们都有参数。它可以找到一个 TList<T>
,这就是它使用的那个。
正如您在评论中提到的,以下变体不会泄漏:
constructor TDerivedGenericObjectList.Create;
begin
inherited Create;
end;
这种语法与裸露相比 inherited
,将找到替换默认参数时匹配的方法。所以单参数构造函数 TObjectList<T>
叫做。
该 文件 有这个信息:
继承的保留字在实现多态行为中起着特殊的作用。它可以在方法定义中出现,有或没有标识符。
如果继承后跟成员的名称,则表示正常的方法调用或对属性或字段的引用,除了搜索引用的成员以封闭方法的类的直接祖先开始。例如,在:
inherited Create(...);
发生在方法的定义中,它调用继承的Create。
当继承后面没有标识符时,它引用与封闭方法同名的继承方法,或者如果封闭方法是消息处理程序,则引用同一消息的继承消息处理程序。在这种情况下,inherited不使用显式参数,而是将与调用封闭方法相同的参数传递给inherited方法。例如:
inherited;
在构造函数的实现中经常发生。它使用传递给后代的相同参数调用继承的构造函数。
你可以使用泛型。没有类型转换和内存泄漏,它工作正常( TObjectList<T>
要么 TObjectDictionary<T>
列表在自由命令上自动销毁内部对象)。
一些技巧:
以下是您的代码示例(使用新构造函数,没有内存泄漏,并且向后兼容旧代码 - 请参阅 GetPerson
):
type
TPerson = class
public
Name: string;
Age: Integer;
function Copy: TPerson;
end;
TMembers = class(TObjectList<TPerson>)
private
function GetPerson(i: Integer): TPerson;
public
property Person[i: Integer]: TPerson read GetPerson;
constructor Create(SourceList: TMembers); overload;
end;
{ TPerson }
function TPerson.Copy: TPerson;
var
person: TPerson;
begin
person := TPerson.Create;
person.Name := Self.Name;
person.Age := Self.Age;
Result := person;
end;
{ TMembers }
constructor TMembers.Create(SourceList: TMembers);
var
person: TPerson;
begin
inherited Create;
for person in SourceList do
begin
Self.Add(person.Copy);
end;
end;
function TMembers.GetPerson(i: Integer): TPerson;
begin
Result := Self[i];
end;
procedure TForm21.Button1Click(Sender: TObject);
var
person: TPerson;
memsList1: TMembers;
memsList2: TMembers;
begin
// test code
memsList1 := TMembers.Create;
person := TPerson.Create;
person.Name := 'name 1';
person.Age := 25;
memsList1.Add(person);
person := TPerson.Create;
person.Name := 'name 2';
person.Age := 27;
memsList1.Add(person);
memsList2 := TMembers.Create(memsList1);
ShowMessageFmt('mems 1 count = %d; mems 2 count = %d', [memsList1.Count, memsList2.Count]);
FreeAndNil(memsList1);
FreeAndNil(memsList2);
end;