为什么这个程序会报告内存泄漏?
{$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.
答案 0 :(得分:14)
您正在调用TObjectList<T>
的无参数构造函数。这实际上是TList<T>
的构造函数,TObjectList<T>
的派生类。
TObjectList<T>
中声明的所有构造函数都接受一个名为AOwnsObjects
的参数,该参数用于初始化OwnsObjects
属性。因为您绕过了该构造函数,OwnsObjects
默认为False
,并且列表的成员不会被销毁。
您应确保调用初始化TObjectList<T>
的{{1}}构造函数。例如:
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
的单个参数构造函数。
documentation有以下信息:
继承的保留字在实现多态行为中起着特殊的作用。它可以在方法定义中出现,有或没有标识符。
如果继承后跟一个成员的名称,它表示一个普通的方法调用或对属性或字段的引用,除了搜索引用的成员以封闭方法的类的直接祖先开始。例如,在:
TObjectList<T>
发生在方法的定义中,它调用继承的Create。
当继承后面没有标识符时,它引用与封闭方法同名的继承方法,或者如果封闭方法是消息处理程序,则引用同一消息的继承消息处理程序。在这种情况下,inherited不使用显式参数,而是将与调用封闭方法相同的参数传递给inherited方法。例如:
inherited Create(...);
在构造函数的实现中经常发生。它使用与传递给后代的相同参数调用继承的构造函数。
答案 1 :(得分:1)
您可以使用泛型。没有类型转换和内存泄漏(TObjectList<T>
或TObjectDictionary<T>
列表在自由命令上自动销毁内部对象),它可以正常工作。
一些提示:
TObjectList<TPerson>
- 像membersList.Free
一样自由销毁人员名单;
TList<TPerson>
- 不要破坏人员名单。您必须创建析构函数并手动释放列表中的每个人;
以下是您的代码示例(使用新构造函数,没有内存泄漏,并且向后兼容旧代码 - 请参阅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;