开发者

DELPHI class with dynamic array has problem with SetLength()

I need to create a class containing an array of record objects but trying to use SetLength raise an Access Violation errror.

Consider the following example of a tree object with fruits.

type
TFruit = record
  color: string;
  weight: double;
end;

type
  TObjectTree = class

  Public
  Fruits: array of TFruit;

  constructor Create;
  procedure AddFruit;
end;

In the implementation, when trying to resize the array of Fruit objects or initializing to nil generates problems.

constructor TObjectTree.Create;
begin
    inherited Create;
    Fruits:=nil;              //Raises an error
end;


procedure TObjectTree.AddFruit(FruitColor: string; FruitWeight: integer);
begin
    SetLength(Fruits, Length(Fruits)+1);  //Raises an error (when I comment Fruits:=nil; in the constru开发者_StackOverflow社区ctor)

    Fruits[Length(Fruits)].color:=FruitColor;      
    Fruits[Length(Fruits)].weight:=FruitWeight;
end;

How can I use dynamic arrays in a class?


Replace

Fruits[Length(Fruits)].color:=FruitColor;      
Fruits[Length(Fruits)].weight:=FruitWeight;

with

Fruits[High(Fruits)].color:=FruitColor;
Fruits[High(Fruits)].weight:=FruitWeight;

then it works.


Something tells me you've neglected to create an instance of TObjectTree. You've declared a TObjectTree variable, but you've either not called TObjectTree.Create, or you've called it directly on the variable you declared instead of assigning a new value to that variable:

var
  Tree: TObjectTree;
begin
  // This is wrong.
  Tree.Create;

  // This is right.
  Tree := TObjectTree.Create;

Without properly instantiating TObjectTree, there is no valid memory to back the Fruits field you attempt to use, so assigning a value to it gives an error.


As an addition to the answers of iamjoosy and Rob Kennedy, I would code this like so:

procedure TObjectTree.AddFruit(FruitColor: string; FruitWeight: integer);
var
  NewCount: Integer;
begin
  NewCount := Length(Fruits)+1;
  SetLength(Fruits, NewCount);
  Fruits[NewCount-1].color := FruitColor;      
  Fruits[NewCount-1].weight := FruitWeight;
end;

It is clearer, in my view, to call Length() just once.

You do not need to assign Fruits := nil in the constructor since that happens automatically. All fields are zero-initialised when an object is instantiated. That said, Fruits := nil should not raise an error. If it does it is probably a result of a memory corruption due to the out-of-bounds array accessing.

A further point to make is that enabling range checking would have resulted in an informative error that would have explained the problem. This is much more helpful than relying on access violations. I can't recommend range checking highly enough.

Finally, the SetLength(..., Length(...)+1) pattern typically leads to very inefficient memory usage and can lead to performance problems for large lists. If you have Delphi 2009+, I would recommend using TList<TFruit> instead.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜