Delphi中是否有一种方法可以实例化所需数量的对象,而不需要迭代?

4
我认为C++支持类似以下内容的东西:
Object objects[100];

这会实例化100个对象,对吗?在Delphi(特别是2007)中是否有可能这样做?除了以下方法之外还有其他的方式吗:
for i:=0 to 99 do
  currentObject = TObject.Create;

或者使用Allocate函数,传递TObject大小的一百倍作为大小值,因为它只是分配内存,而不是将内存划分并“分配”给对象。

如果我的假设是c++实例化是瞬间完成而不是在幕后进行迭代,那我道歉。


4
我认为你不能简单地说“TObject ObjList[100];”就创建了100个TObject,你只有一个包含100个指针的列表,应该将它们初始化为NIL|null,并且分配一个对象给列表中的项,所以我认为没有快捷的方法。 - user497849
3
任何这样的语法很可能只是在迭代实例化的基础上添加了一些糖。你需要这样的语法有什么原因,而不是使用迭代? - Larry Lustig
3
无论实例化是如何触发的,您仍然需要支付所有对象的实例化费用。循环所消耗的资源很少。如果性能是一个问题,并且对象相对简单,请考虑使用Delphi记录。 - Larry Lustig
2
在C++中,TObject* data[100]; 只分配对象指针数组。无论如何,您必须使用 data[i] = new TObject(); 语法(或类似语法)创建对象实例,就像在Delphi或其他任何语言中一样。 - teran
1
@Vlad:是的,C++确实支持Object objects[100],前提是Object不是TObject的派生类。TObject必须在堆上分配,这是Delphi的要求。C++中没有其他要求。 - Remy Lebeau
显示剩余6条评论
5个回答

5
您所寻找的是不可能的,因为:
  • Delphi不支持静态(堆栈分配)对象。
  • Delphi对象没有默认构造函数,无法被编译器自动调用。

因此,这不是“语法糖”的缺失。


为了完全透明:

  • Delphi还支持传统的“旧对象模型”(Turbo Pascal对象模型),允许静态分配对象;
  • 动态对象分配本身并不妨碍自动对象实例化语法,但使这样的语法不可取;
  • 自动对象实例化语法是不可能的,因为Delphi没有默认构造函数:Delphi编译器从不隐式实例化对象,因为它不知道要调用哪个构造函数。

1
@Serg Warren刚刚说他不想让这种情况发生。我认为我同意。我只是在说,在问题的背景下,对于堆栈分配对象的缺乏支持并不是一种限制。此外,我认为“静态”这个词是错误的。你不是应该指“自动”的吗? - David Heffernan
1
@NickHodges - XE3中TForm类的默认构造函数是什么? - kludg
1
@Nick 不,类构造函数是不同的。它们用于实例化类。默认构造函数实例化实例。类构造函数是多年前引入的,而不是在XE3中引入的。 - David Heffernan
1
@LeonardoHerrera 没错,你没有错过任何东西。什么也没有。Nick很困惑。 - David Heffernan
1
@Goran_Mandic:是的,但我不知道如何正确释放这些N个对象 - 我从来没有这样做过,因为我从来没有需要。 - kludg
显示剩余13条评论

5
虽然您无法使用对象来完成想要的操作,但如果您的对象相对简单,您可能可以通过使用记录数组来实现想要的功能。
Delphi中的记录可以具有属性(包括设置器和获取器)以及类和实例方法。声明时它们会自动生成,因此声明一个记录数组将会自动创建所有记录,无需迭代。
更多信息请参见:http://docwiki.embarcadero.com/RADStudio/XE3/en/Structured_Types#Records_.28advanced.29。(我不确定新功能是在哪个版本之后加入的,可能是2007版本之后)。

在BDS 2006中,记录已经可以拥有方法。如果有人将“object”替换为“thingy”,那么这就是答案。 - balazs
这可能会有所帮助。我会在周一研究一下。谢谢,+1。 - programstinator

2

除了迭代,我不知道有什么非hacky的方法来做到这一点:

var
  MyObjects: array[0..99] of TMyObject;
  i: Integer;
begin
  for i := 0 to 99 do
    MyObjects[i] := TMyObject.Create;
end;

2
这一切都很好和正确,但这与我所要求的相反 :) - programstinator
1
@Goran:不,这是对“是否可以在Delphi(特别是2007)中实现此功能?”的回答,附加了一些有效的Delphi语法。 :-) - Uli Gerhardt
1
不,Uli,这不是那个问题的答案。在那个问题中,单词this指的是在不迭代的情况下分配许多对象的任务。你的回答忽略了这个细节,本质上与已经在问题中给出的Delphi代码没有什么区别。对于这个问题,正确的答案是“不”。 - Rob Kennedy

1

这个声明不会创建100个对象,它只会给你一个包含100个指向无用对象的引用数组。

创建一个对象是一个两步过程。第一步是分配内存(你的代码也没有做到),第二步是调用构造函数(Create方法)来初始化该内存、创建其他对象等等。

分配部分可以在循环外完成,但需要调用构造函数来初始化每个实例。

许多VCL类没有额外的构造函数。它们只有什么都不做的空构造函数。在这种情况下,就不需要调用它。

例如,要获取一个字符串列表数组,您可以使用以下代码,从此示例进行了调整

type
  TStringListArray = array of TStringList;v
var
  Instances: array of Byte;

function GetStringLists(Number: Integer): TStringListArray;
var
  i: Integer;
begin
  // Allocate instance memory for all of them
  SetLength(Instances, Number * TStringList.InstanceSize);
  // Zero the memory.
  FillChar(Instances[0], Length(Instances), 0);
  // Allocate array for object references.
  SetLength(Result, Number);

  for i := 0 to High(Result) do
  begin
    // Store object reference.
    Result[i] := @Instances[i * TStringList.InstanceSize];
    // Set the right class.
    PPointer(Result[i])^ := TStringList;
    // Call the constructor. 
    Result[i].Create;
  end;
end;

要获取一个包含100个字符串列表的数组:

var
  x: TStringListArray;
begin
  x := GetStringLists(100);

因此,尽管这个过程可能会为您节省一些时间(可忽略不计),并且在理论上可能更加内存高效(减少碎片化),但您仍然需要循环。没有捷径。


我不是那个给你点踩的人,但是针对上述的 TStringListArray,你还需要一些非平凡的终止代码。你不能调用 StringList[I].Free - kludg
2
我怀疑你能否释放单个TStringList对象 - 我不知道FastMM的反应会是什么。 - kludg
3
@Serg 天哪,你说得对。调用 x[20].Free 会立即导致访问冲突。:s 这个问题不容易解决。你不能调用 Free 或 Destroy。你可以调用 CleanupInstance,但它不会调用 TStringList 所有的虚析构函数。 - GolezTrol
1
我不明白为什么一个简单的 SetLength(x, 100)(其中 x 是 TStringListArray)不能分配一个由 100 个 TStringList 组成的数组。我仍然盯着这段代码,试图弄清楚为什么需要这种“hack”。 - kobik
1
你的代码不是调用了 Result[i].Create 100 次吗?因此:SetLength(x, 100);for i := 0 to High(x) do x[i] := TStringList.Create; 不会创建相同的数组吗?也许我需要在 SO 上问一下我的问题... :) - kobik
显示剩余8条评论

1

在Delphi中这是有点可能的(但在Delphi环境中并不是很实用,除非你正在编写自定义内存管理器)。创建对象实例是一个两步过程 - 为对象分配内存,并在该内存中构造对象的成员。没有什么要求给定对象的内存必须单独分配。您可以分配一个较大的内存块,并在该块内构造多个对象,利用Delphi的一个特性,如果从实例变量而不是类类型调用构造函数,则像普通方法一样调用构造函数,例如:

var
  objects: array of Byte;
  obj: TSomeClass;
begin
  SetLength(objects, 100 * TSomeClass.InstanceSize); 
  FillChar(objects[0], 0, Length(objects));
  for i := 0 to 99 do
  begin
    obj := TSomeClass.InitInstance(@objects[i * TSomeClass.InstanceSize]));
    obj.Create;
  end;
  ...
  for i := 0 to 99 do
  begin
    obj := TSomeClass(@objects[i * TSomeClass.InstanceSize]);
    obj.CleanupInstance;
  end;
  SetLength(objects, 0);
end;

这与在 C++ 中声明对象实例数组时的操作并没有太大区别,只不过 C++ 支持静态声明数组,并且会自动调用构造函数和析构函数,而 Delphi 不支持。

有许多第三方实现可以让您在堆栈或用户定义的缓冲区中分配对象,例如:

堆栈上的对象:Delphi 遗物

在堆栈、指定内存地址或通过任何内存管理器分配对象

这只是其中的几个例子。


使用 SetLength() 为动态数组分配的新内存在旧版 Delphi 中不能保证为零。手动将其清零不会有任何影响。 - Remy Lebeau
真的吗?哪些版本不会将其归零? - David Heffernan
根据D7文档:“对于长字符串或动态数组变量,SetLength将S引用的字符串或数组重新分配到给定长度。保留字符串中的现有字符或数组中的元素,但新分配空间的内容未定义。唯一的例外是在增加必须初始化类型的元素(字符串、变体、变体数组或包含这些类型的记录)的动态数组的长度时。当S是必须初始化类型的动态数组时,新分配的空间设置为0或nil。” - Remy Lebeau
我刚刚检查了实现。新内存总是被初始化为零。这表明您不能仅仅依赖于文档。 - Remy Lebeau

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