Zagnieżdżone tablice elementów tego samego typu

0

Potrzebuję zrobić coś w tym stylu:

type TNested = record
  Children: array of TNested;
end;
 
function Nested(const Children: array of TNested): TNested;
begin
  SetLength(Nested.Children, Length(Children));
  Move(Children[0], Nested.Children[0], Length(Children)*SizeOf(TNested));
end;

Jednakże...

kod wynik
http://ideone.com/2qrIMe
var n: TNested;
begin
  n := Nested([
  ]);
end.

| spoko
http://ideone.com/XX3Uqe

var n: TNested;
begin
  n := Nested([
    Nested([])
  ]);
end.

| spoko
http://ideone.com/JSDtUj

var n: TNested;
begin
  n := Nested([
    Nested([
      Nested([])
    ])
  ]);
end.

|

Runtime error 216 at 
  $0805B96A
  $0805B96A
  $08063803...

Co robię nie tak?

3

Zobacz tutaj - http://ideone.com/aRcucO

type
  TNode = record
    SubNodes: array of TNode;
  end;

  function Node(ASubNodes: array of TNode): TNode;
  var
    intNodeIdx: Integer;
  begin
    SetLength(Result.SubNodes, Length(ASubNodes));

    for intNodeIdx := 0 to High(ASubNodes) do
      Result.SubNodes[intNodeIdx] := ASubNodes[intNodeIdx];
  end;

var
  nodeRoot: TNode;
begin
  nodeRoot := Node([
    Node([
      Node([
        Node([
          Node([
            Node([
              Node([
                Node([
                  Node([
                  ])
                ])
              ])
            ])
          ])
        ])
      ])
    ])
  ]);

  ReadLn();
end.

W Twoim przykładzie procedura Move głupieje, bo co prawda zna rozmiar typu TNested, ale nie zna rozmiaru pola Children; W ten sposób kopiuje tyle pamięci, ile zajmowały by wszystkie elementy macierzy z parametru, gdyby wszystkie posiadały puste macierze w sobie :]

PS: Koniecznie używaj ukrytej zmiennej Result;
____Edit: Funkcja używająca procedury Move powinna wyglądać tak (dla mojego przykładu):

type
  TNode = record
    SubNodes: array of TNode;
  end;

  function Node(ASubNodes: array of TNode): TNode;
  var
    intNodeIdx: Integer;
    intCapacity: Integer = 0;
  begin
    for intNodeIdx := 0 to High(ASubNodes) do
      intCapacity += Length(ASubNodes[intNodeIdx].SubNodes) * SizeOf(TNode);

    SetLength(Result.SubNodes, Length(ASubNodes));
    Move(ASubNodes[0], Result.SubNodes[0], intCapacity);
  end;

Program będzie działał prawidłowo, choć sam nie byłem pewny poprawności jego działania, dopóki nie sprawdziłem alokacji pamięci i ewentualnych mem-leaków, za pomocą modułu heaptrc - output:

C:\Documents and Settings\furious programming\Pulpit\Foo>foo.exe

Heap dump by heaptrc unit
8 memory blocks allocated : 96/128
8 memory blocks freed     : 96/128
0 unfreed memory blocks : 0
True heap size : 98304 (128 used in System startup)
True free heap : 98176

C:\Documents and Settings\furious programming\Pulpit\Foo>

Czyli jak widać działa prawidłowo; Co i tak nie zmienia faktu, że pierwsza wersja jest bezpieczniejsza i niezależna od typów danych dla pól.

0

PS: Koniecznie używaj ukrytej zmiennej Result;

Sorka, przyzwyczajenie po szybkich przykładach jakie zdarzało mi się dłubać bez {$mode objfpc}{$H+} - Result nie działało, więc szukałem za czymś innym.

Funkcja używająca procedury Move powinna wyglądać tak [...]

Skoro i tak trzeba zaprzęgnąć do tego pętlę, to nie ma co angażować Move - ewentualnie kiedyś skomląc o optymalizacje sobie o tym przypomnę i zobaczę.

Szybko poszło jak na 4 godzinę użerania się z namierzaniem problemu :P

0

Najlepiej w ogóle zapomnij o Move, dlatego że przykład jaki podałem działa tylko i wyłącznie dla rekordu posiadającego jedno pole jako macierz self-rekordów; Dodasz kolejne pole i się wywali (tak sądzę, bo nie testowałem); Zresztą sam powinieneś się o tym za niedługo przekonać :]

Edit: Tu masz przykład dla rekordów z dwoma polami - http://ideone.com/Nda4Jm:

type
  TNode = record
    Name: String;
    SubNodes: array of TNode;
  end;

  function Node(const AName: String): TNode;
  begin
    Result.Name := AName;
    Result.SubNodes := nil;
  end;

  function Node(const AName: String; ASubNodes: array of TNode): TNode;
  var
    intNodeIdx: Integer;
  begin
    Result.Name := AName;
    SetLength(Result.SubNodes, Length(ASubNodes));

    for intNodeIdx := 0 to High(ASubNodes) do
      Result.SubNodes[intNodeIdx] := ASubNodes[intNodeIdx];
  end;

var
  nodeRoot: TNode;
begin
  nodeRoot := Node('Level 1, Root 1',
  [
    Node('Level 2, Leaf 1'),
    Node('Level 2, Leaf 2'),
    Node('Level 2, Root 1',
    [
      Node('Level 3, Leaf 1'),
      Node('Level 3, Leaf 2'),
      Node('Level 3, Root 1',
      [
        Node('Level 4, Leaf 1'),
        Node('Level 4, Leaf 2')
      ])
    ]),
    Node('Level 2, Leaf 3'),
    Node('Level 2, Leaf 4'),
    Node('Level 2, Root 2',
    [
      Node('Level 3, Leaf 1'),
      Node('Level 3, Leaf 2')
    ])
  ]);
end.

Jak widać też działa prawidłowo i nie ma wyjątków czy wycieków pamięci; Najważniejsze w tym sposobie jest to, że modyfikacja zawartości struktury TNode nie oznacza poprawiania reszty kodu; Jeśli doda się nowe pole do rekordu to funkcje Node same się dostosują.

1 użytkowników online, w tym zalogowanych: 0, gości: 1