【问题标题】:Is there a way to instantiate a desired number of objects in Delphi, without iterating?有没有办法在 Delphi 中实例化所需数量的对象,而无需迭代?
【发布时间】:2012-10-26 13:16:43
【问题描述】:

我认为 C++ 支持以下内容:

Object objects[100];

这将实例化 100 个对象,对吗?是否可以在 Delphi(特别是 2007 年)中做到这一点?除了:

for i:=0 to 99 do
  currentObject = TObject.Create;

或使用Allocate 函数,传递的大小值是TObject 大小的一百倍,因为这只是分配内存,它实际上并没有划分内存并将其“分配”给对象。 如果我假设 c++ 实例化是即时的而不是底层迭代,我深表歉意。

【问题讨论】:

  • 我认为你不能简单地说“TObject ObjList[100];”并且您已经创建了 100 个 TObject,您只有一个包含 100 个指针的列表,这些指针应该被初始化为 NIL|null 和/或将一个对象分配给列表中的一个项目,所以我认为没有快速的方法。
  • 任何这样的语法都可能被撒在迭代实例化之上。您是否有理由需要这样的语法,而不是迭代?
  • 您仍然需要为所有对象的实例化付费。无论该实例化是如何触发的。循环消耗的资源是最小的。如果性能有问题且对象相对简单,请考虑改用 Delphi 记录。
  • TObject* data[100]; 在 c++ 中仅分配指向对象的指针数组。无论如何,您必须像在 Delphi 或任何其他语言中一样使用 data[i] = new TObject(); 语法(或类似的方式)创建对象实例;
  • @Vlad:是的,C++确实支持Object objects[100],前提是Object不是TObject的后代。 TObject 必须在堆上分配,这是 Delphi 的要求。 C++ 中没有其他任何东西有这个要求。

标签: delphi delphi-2007


【解决方案1】:

你正在寻找的东西是不可能的,因为

  • Delphi 不支持静态(堆栈分配)对象。
  • Delphi 对象没有可被编译器自动调用的默认构造函数。

所以这并不是缺少“糖语法”。


为了完整披露:

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

【讨论】:

  • 我想要求证明这一点是可笑的。我会相信你的话,谢谢!
  • 确实不能做自己想做的事,但我认为这个答案中的推理并不准确。没有理由不能实现支持自动实例化堆上对象的语言扩展。
  • @DavidHeffernan - 注意第二点。 Delphi 编译器从不实例化对象——它不知道要调用什么构造函数。
  • 我讨厌任何违反该规则的 Delphi 扩展。我们创造他们并释放他们,我们喜欢这样。任何想要走上 OP 建议的道路的人都会走上托管语言的道路,我希望 Delphi 永远不会走这条路。
  • @Serg 第二点有效,但第一点似乎无效。
【解决方案2】:

虽然你不能用对象做你想做的事,但如果你的对象比较简单,你也许可以通过使用记录数组得到你想要的。

Delphi 中的记录可以有属性(包括 setter 和 getter),以及类和实例方法。它们在声明时会自动创建,因此声明它们的数组将无需迭代即可全部创建。

欲了解更多信息:http://docwiki.embarcadero.com/RADStudio/XE3/en/Structured_Types#Records_.28advanced.29

(我不确定新功能是什么时候添加到 Delphi 中的,可能是在 2007 版本之后)。

【讨论】:

  • 在 bds 2006 记录中已经可以有方法。如果有人将问题中的object 替换为thingy,这就是答案。
  • 这可能会有所帮助。周一会调查。谢谢,+1。
【解决方案3】:

除了迭代之外,我不知道任何非 hacky 的方法:

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

【讨论】:

  • 这一切都很好而且正确,但这与我的要求相反:)
  • @Goran: 不,这是Is it possible to do this in Delphi (specifically 2007)? 的答案,使用了一些有效 Delphi 语法。 :-)
  • 不,Uli,这不是答案。在那个问题中,this 一词指的是分配大量对象而不迭代 的任务。您的答案忽略了该细节,并且与问题中已经给出的 Delphi 代码本质上没有什么不同。该问题的正确答案是
【解决方案4】:

该声明不会创建 100 个对象,它只会为您提供一个包含 100 个对象引用的数组,这些引用指向没有用处。

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

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

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

例如,要获取一个字符串列表数组,您可以使用以下代码,adjusted from this example

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
  • 我怀疑你可以释放单个 TStringList 对象 - 我不知道 FastMM 的反应会是什么。
  • @Serg 该死的,你是对的。调用 x[20].Free 会立即产生访问冲突。 :s 这不容易解决。您不能调用 Free 或 Destroy。您可以调用 CleanupInstance,但它不会调用 TStringList 具有的重写析构函数。
  • 我不明白为什么一个简单的SetLength(x, 100)(x 是TStringListArray)不会分配100 个TStringLists 的数组。我仍在盯着这段代码,试图弄清楚为什么需要这个“hack”。
  • 好吧,你的代码不是调用 Result[i].Create 100 次吗?所以:SetLength(x, 100);for i := 0 to High(x) do x[i] := TStringList.Create; 不会创建相同的数组?也许我需要问我自己的问题... :)
【解决方案5】:

在 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 不支持。

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

Objects on the Stack: A Delphi Relic

Allocate objects on stack, at specified memory address, or through any memory manager

仅举几个例子。

【讨论】:

  • 使用SetLength() 为动态数组分配的新内存在旧版 Delphi 中不保证为零。手动将其归零并没有什么坏处。
  • 真的吗?哪些版本不归零?
  • 根据 D7 文档:“对于长字符串或动态数组变量,SetLength 将 S 引用的字符串或数组重新分配给给定长度。字符串中的现有字符或数组中的元素是保留,但新分配空间的内容是未定义的。一个例外是增加动态数组的长度时,其中元素是必须初始化的类型(字符串、变体、变体数组或包含此类类型的记录)。当 S 是必须初始化的类型的动态数组时,新分配的空间设置为 0 或 nil。"
  • 不过,我刚刚检查了实现。新内存总是零初始化。只是表明你不能总是只依赖文档。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多