把TArray<X>转换成X类型的数组是安全的吗?

16
今天我发现了一个编译器的错误(QC#108577)。
以下程序无法编译:
program Project1;
{$APPTYPE CONSOLE}

procedure P(M: TArray<TArray<Integer>>);
begin
  SetLength(M, 1, 2);
end;

begin
end.
编译器在SetLength这行代码上出错并显示以下错误信息:
[dcc32 Error] E2029 ')' expected but ',' found

我知道我可以像这样修复它:

procedure P(M: TArray<TArray<Integer>>);
var
  i: Integer;
begin
  SetLength(M, 1);
  for i := low(M) to high(M) do
    SetLength(M[i], 2);
end;

但我自然希望避免不得已采用这种方法。

以下变量编译并似乎可以正常工作:

procedure P(M: TArray<TArray<Integer>>);
type
  TArrayOfArrayOfInteger = array of array of Integer;
begin
  SetLength(TArrayOfArrayOfInteger(M), 1, 2);
end;

我对动态数组的实现细节、TArray<T>类型转换、引用计数等方面了解不足,无法确信这样做是否安全。

有没有人可以确认一下,在运行时这个方法是否能产生正确的代码?


1
System.pas 定义了 TArray<T> = array of T;,因此我期望硬转换 应该 能够工作。 - afrazier
1
无论哪种情况,最终都会进入“DynArraySetLength”(至少有一个一维数组),所以我同意上述观点。 - Sertac Akyuz
我认为 System 只是特殊的地方在于预定义的位和编译器魔法会被编译器自动链接到其中?System.pas 中几乎所有的代码仍然必须是适当可编译的 Delphi 代码,对吗? - afrazier
@afrazier的意思是可编译性并不意味着实现和任何代码生成。 - David Heffernan
1
该漏洞已在XE4中修复。 - LU RD
显示剩余20条评论
3个回答

19
编译器内置过程 SetLength 在堆栈上构建一个动态数组的维度,并对任何动态数组调用 DynArraySetLength, 不论它是否为泛型。如果一个泛型数组与常规动态数组在结构上不兼容,那么可能不会调用相同的设置长度的实现。
事实上,DynArraySetLength文档提供了 SetLength 作为多维数组的替代方案。可以使用 DynArraySetLength 替代类型转换,但我不认为有理由更喜欢其中之一。

@David - 不用谢!不过无论如何,我仍然希望有人能提出更具体的声明。 - Sertac Akyuz

3
根据泛型实现的设计,使用手动映射到array of array of Integer将起作用。
但是在这里使用泛型没有任何好处!
只需编写以下代码:
type
  TArrayOfArrayOfInteger = array of array of Integer;

procedure P(M: TArrayOfArrayOfInteger);
begin
  SetLength(TArrayOfArrayOfInteger, 1, 2);
end;

请注意,像 TArray<>array of .. 这样的内容会按值传递,并在堆栈上复制,除非您指定 constvar
procedure P(var M: TArrayOfArrayOfInteger);
begin
  SetLength(TArrayOfArrayOfInteger, 1, 2);
end; // now caller instance of the parameter will be resized

var A: TArrayOfArrayOfInteger;
...
A := nil;
P(A);
assert(length(A)=1);
assert(length(A[0])=2);

你有任何文件可以支持“按设计”这一断言吗?在这里使用泛型有好处。在一个泛型容器类中,我可以编写这样的函数:function ToArray: TArray<T>。很明显,如果不使用泛型TArray<T>,我无法做到这一点。我知道所有关于var和const的事情。我的例子显然是无用和毫无意义的。它被设计成最小可能的例子,以说明错误。考虑到代码无法运行,担心运行时性能似乎是毫无意义的。 - David Heffernan
@David 使用泛型,您可以声明一个函数,例如 function ToArray: array of T,并且使用嵌套类型甚至允许您对其进行命名。 - Arioch 'The
@Arioch 不,那段代码无法编译。没有必要给TArray<T>命名。它已经有一个名称了。一个名称就足够了。 - David Heffernan
@David,是的,它已经有一个名字了,名字就是“T数组” ;-) 我不明白为什么你要保护两种声明数组的方式的隔离。特别是因为这种隔离只是由70年代的“Pascal报告”支持的,而这个报告已经被大多数其他类型(如集合和范围)所颠覆。 - Arioch 'The
这个不会编译,根据你上面的评论:function ToArray: array of T。你过去的粘贴板中的TA是不需要的。使用TArray<T>显然更简单。在我的建议中没有特殊情况。 - David Heffernan
显示剩余3条评论

2

最近我发现在C++中,DynamicArray<T>TArray<T> 实际上是不同的实现(DynamicArray 是一个独立的类,而 TArray 是一个 TObject 子类),这意味着在Delphi中,array of TTArray<T> 也有一些实现差异。至少它们产生不同类型的RTTI。这是我在一些C++代码中遇到问题的根本原因。当Delphi编译器开始为Delphi array of ... 类型输出 TArray typedefs 而不是 DynamicArray typedefs时,我的代码开始失败。


我完全不了解Emba的C++。在Emba C++中,DynamicArray<T>TArray<T>是什么,它们与Delphi有什么关系? - David Heffernan
1
它们是模板类,直接对应于 Delphi 的 array of ...TArray 类型,就像 AnsiStringUnicodeStringVariant 类直接对应其 Delphi 对应项一样。它们都是 C++ 类,允许 C++ 和 Delphi 之间交换兼容的数据。 - Remy Lebeau
在EMB中,这又是一个“左手不知右手在做什么”的案例。在Delphi中,type TArray<T> = array of T的定义并不像TList<T>那样是类。而C++又一次与Delphi不同。 - Arioch 'The
2
但是 TArray<T> 是一个泛型,所以我相信这也会导致问题。我猜想 TArray<Byte>array of Byte 实际上是不同的,因为这个原因。这解释了它们为什么有不同的 RTTI。 - Remy Lebeau

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