Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Firefly

Alter Hase

  • »Firefly« ist der Autor dieses Themas

Beiträge: 484

Wohnort: Irgendwoundnirgendwo

  • Private Nachricht senden

1

23.02.2011, 22:05

n-ary Tree

Hallo Community,
im Zuge eines Projektes würde ich gerne einen Scenegraphen implementieren der auf einem n-ary Tree aufbaut. Konkret, dass ein Node beliebig viele Subnodes beinhalten kann.
Die Überlegung ist dabei, die Kinder als multidimensionales Array zu realisieren und alles auf den heap auszulagern.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
class CSceneNode
{
protected:
    //members
    
    //Array of Children...
    CSceneNode      **ppChildren;
    int             iChildrenCount;

    //primitive
    IIntersectable  *pData;


    //recursive functions
    CSceneNode* Copy(CSceneNode *node)
    {
        CSceneNode *res = new CSceneNode();

        //copy data directly

        //....todo
        res->Data = node->Data;
        res->iChildrenCount = 0;
        res->ppChildren = NULL;
        
        //is node leaf?
        if(!node->isLeaf())
        {
            //traverse SceneNode
            res->ppChildren = new CSceneNode*[node->iChildrenCount];
            res->iChildrenCount = node->iChildrenCount;
            //alle Kinder kopieren
            for(int i = 0; i < node->iChildrenCount; i++)
            {
                res->ppChildren[i] = Copy(node->ppChildren[i]);
            }
        }   
        return res;
    }

    void printR(CSceneNode *node, int level)
    {
        std::cout<<"Value is "<<node->Data<<" on level "<<level<<std::endl;
        if(!node->isLeaf())
        {
            for(int i = 0; i < node->iChildrenCount; i++)
                printR(node->ppChildren[i], level + 1);
        }
    }

    CSceneNode* clear(CSceneNode *node)
    {
        //is Leaf?
        if(node->isLeaf())
        {
            //only delete Data 
            //and then delete Pointer
            SAFE_DELETE(node->pData);
            //delete node himself!
            SAFE_DELETE(node);

            //node should be NULL!
            if(node != NULL)std::cout<<"Bad error!"<<std::endl;

            //done!
            return NULL;
        }
        else
        {
            //this is no leaf, so delete its children
            for(int i = 0; i < node->iChildrenCount; i++)
            {
                node->ppChildren[i] = clear(node->ppChildren[i]);

                //child shall be NULL!
                if(node->ppChildren[i] != NULL)std::cout<<"Bad error!"<<std::endl;

                //deleted...
            }

            //so now delete array of pointers!
            SAFE_DELETE_A(node->ppChildren);
            node->iChildrenCount = 0;

            //array shall be NULL!
            if(node->ppChildren != NULL)std::cout<<"Bad error!"<<std::endl;

            //secure NULL!
            node->ppChildren = NULL;

            //delete Node Data, and Node himself!
            SAFE_DELETE(node->pData);
            //delete node himself!
            SAFE_DELETE(node);

            //node should be NULL!
            if(node != NULL)std::cout<<"Bad error!"<<std::endl;

            //done!
            return NULL;
        }
    }
public:
    int Data;

    //constructor
    CSceneNode()
    {
        ppChildren = NULL;
        iChildrenCount = 0;
        pData = NULL;
    }

    CSceneNode(int d)   {ppChildren = NULL; iChildrenCount = 0; Data = d; pData = NULL;}

    //destructor
    ~CSceneNode()
    {
        std::cout<<"dtor of "<<Data<<" with "<<iChildrenCount<<" children"<<std::endl;
        
        SAFE_DELETE(pData);

        for(int i = 0; i < iChildrenCount; i++)
        {
            SAFE_DELETE(ppChildren[i]);
        }
        iChildrenCount = 0;
        SAFE_DELETE_A(ppChildren);

        std::cout<<ppChildren<<std::endl;
            
        //Clear();
    }

    //make this SceneNode, a copy of another
    void        CopyOf(CSceneNode *node)
    {
        Data = node->Data;
        
        //is node leaf?
        if(node->isLeaf())
        {
            //copy data directly

            //....todo
            Data = node->Data;
        }
        else
        {
            //traverse SceneNode
            ppChildren = new CSceneNode*[node->iChildrenCount];
            iChildrenCount = node->iChildrenCount;

            //alle Kinder kopieren
            for(int i = 0; i < node->iChildrenCount; i++)
            {
                ppChildren[i] = Copy(node->ppChildren[i]);
            }
        }       
    }

    //add an element
    void        Append(CSceneNode& node)
    {
        //extend array
        CSceneNode **tmp = new CSceneNode*[iChildrenCount + 1];
        
        //copy array
        for(int i = 0; i < iChildrenCount; i++)
        {
            tmp[i] = ppChildren[i];
        }

        //delete old pointer
        SAFE_DELETE_A(ppChildren);
        
        //set node
        (tmp[iChildrenCount]) = new CSceneNode();

        if(!node.isLeaf())
        {
            CSceneNode _node = node;

            //*(tmp[iChildrenCount]) = node;
            tmp[iChildrenCount] = Copy(&_node);
        }
        else *(tmp[iChildrenCount]) = node;


        //inc count
        iChildrenCount++;

        //set pointer
        ppChildren = tmp;
    }

    void print()
    {
        printR(this, 0);
    }

    void    Clear()
    {
        //delete via clear!
        if(isLeaf())
        {
            //only delete Data 
            //and then delete Pointer
            SAFE_DELETE(pData);
                        
        }
        else
        {
            //this is no leaf, so delete its children
            for(int i = 0; i <iChildrenCount; i++)
            {
                ppChildren[i] = clear(ppChildren[i]);

                //child shall be NULL!
                if(ppChildren[i] != NULL)std::cout<<"Bad error!"<<std::endl;

                //deleted...
            }

            //so now delete array of pointers!
            SAFE_DELETE_A(ppChildren);
            iChildrenCount = 0;

            //array shall be NULL!
            if(ppChildren != NULL)std::cout<<"Bad error!"<<std::endl;

            //secure NULL!
            ppChildren = NULL;

            //delete Node Data, and Node himself!
            SAFE_DELETE(pData);
        }
    }

    //Helperfunction
    bool        isLeaf()    {return ppChildren == NULL; }
};


Mein Testprogramm sieht dazu wie folgt aus:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
CSceneNode sub = CSceneNode(3);
    
sub.Append(CSceneNode(4));
sub.Append(CSceneNode(5));
    
CSceneNode top = CSceneNode(1);
top.Append(sub);

std::cout<<"exit<<std::endl;


Wenn nun die main funktion verlassen wird, wird logischerweise der Destruktor aufgerufen. Iwann stürzt dann jedoch das Programm ab, da es Speicherbereiche zu löschen versucht, die schon freigegeben wurden. Um das Ganze besser nachzuvollziehen hab ich in dem Destruktor eine kleine Ausgabe reingebastelt, die den Verlauf ausgibt. (siehe Anhang)
Deshalb die Frage: Weiß jemand wie man zu geg. Problem einen funktionierenden Destruktor aufbaut?

Vielen Dank für alle konstruktiven Beiträge!

Firefly
»Firefly« hat folgendes Bild angehängt:
  • sppro.jpg

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

2

23.02.2011, 22:13

Ich empfehle dir die Nutzung von Containerklassen (z.b. std::list oder std::vector). Das mit den Arrays selbst anlegen ist ganz nett, aber längst nicht so handlich wie entsprechende Containerklassen. Nebenbei würde dein Code auf den Bruchteil schrumpfen und im Endeffekt arbeitet man bei "höheren" Anwendungen seltenst nicht mit Containern.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

3

23.02.2011, 22:20

Also rein logisch ist das ja kein Problem.. Darum verstehe ich dich nicht ganz.. Wenn du irgendwo in dem Baum ein Element entfernst (also deletest), dann machst du im Destruktor zuerst mal dem Vater klar, dass du jetzt tot bist, dann zerstörst du alle Kinder und am Ende noch das, was von dir übrig ist.

Potentielle Fehlerquellen sehe ich bei dir einige. Sind z.B alle Zeiger auf 0 initialisiert (auch die in den Arrays?). Wenn du ein Element löscht solltest du das dem Vater mitteilen, weil der dich bei seiner Zerstörung ebenfalls nochmal löschen will, wenn der dich noch als Kind hat.

Mein Vorschlag für das ganze:
Fang nochmal an und benutze eine Listen Implementierung (z.B std::list) für die Kinder. Die Daten kannst du automatisch (also ohne new) verwalten lassen (oder zumindest mit einem Smart Pointer verwalten). Dann hast du lediglich noch den eigentlichen Baum selbst zu verwalten (obwohl du auch da etwas mit RAII machen kannst, damit du da nicht die Speicherverwaltung selbst machen musst).

Der Destruktor sollte sich dann auf so etwas reduzieren:

C-/C++-Quelltext

1
2
3
4
CSceneNode::~CSceneNode()
{
  parent.set_dead(this);
}

Der Rest wird dann automatisch zerstört. Sieht doch gleich viel weniger Fehleranfällig aus. ;)

Firefly

Alter Hase

  • »Firefly« ist der Autor dieses Themas

Beiträge: 484

Wohnort: Irgendwoundnirgendwo

  • Private Nachricht senden

4

23.02.2011, 22:28

Das hatte ich mir auch schon überlegt, das ganze nur mit STL containern neu zu schreiben. Allerdings ist der Scenegraph häufig benutzt und Arrays haben eine bessere Performance als Container. Weil der Graph viele Objekte beinhalten wird(solln Raytracer werden) würde ich gerne mit Arrays arbeiten. Das interessante ist, dass wenn ich die Clear() Funktion verwende, jeglicher Speicher aufgeräumt wird. Nur derselbe Code im Destruktor erzeugt Fehler.

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

5

23.02.2011, 22:35

Naja. Das ganze lässt sich ja gut debuggen. Also schau einfach mal genau wo der Fehler auftritt. Dann findest du auch deinen Fehler.

btw:
Ich würde fast jede Wette eingehen, dass wenn du ein std::vector anstatt deinem Code benutzt du keinen Unterschied feststellen wirst (in optimiertem Zustand). Je nach Verwendung wird er sogar schneller sein (wegen der ganzen Kopiererei, die du machst wird bei Fällen, wo viel angefügt/gelöscht wird vector schneller sein).

Firefly

Alter Hase

  • »Firefly« ist der Autor dieses Themas

Beiträge: 484

Wohnort: Irgendwoundnirgendwo

  • Private Nachricht senden

6

23.02.2011, 22:47

Ich will den Baum agr nicht groß verändern, nur konstruieren um ihn schnell durchlaufen zu können. Debuggt hab ich das ganze schon lange, bin aber noch zu keiner Lösung gekommen.

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

7

23.02.2011, 23:33

Ich habe oben bereits meine Vermutung geschrieben woran es liegen kann.. Und wo ich mir jetzt den Code ein wenig angeschaut habe hat sich meine Vermutung bestätigt..

Lies meinen Post und überleg dir nochmal ganz genau wie die Lebenszeiten der Objekte ausschauen und wer wann noch eine Referenz auf wen hat und wann die ungültig werden.
Du kannst dir das ja überlegen indem du mal den Baum zeichnest und schaust welche Objekte wann zerstört werden und was dann alles beachtet werden muss.

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

8

24.02.2011, 00:23

STL array im Releasemodus mit gesetzen _SECURE_SCL=0; ist kaum zu schlagen. Außerdem bezweifel ich, dass Performance dein Hauptproblem ist.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

9

25.02.2011, 10:51

:D Bei dem Code glaub ich das auch nicht ...

eh zum stil: Initialisierungsliste, Konstruktor mit Parametern, const-correctness, iterator-konzept und nja ansonsten sieht dein Löschen in der tat komisch aus. Entweder kapsel es dir selbst in ne extra-klasse und hols aus dem scenegraph raus oder nehm stl-container ... was dafür spricht vgl. nox u. drakon ;)
Devil Entertainment :: Your education is our inspiration
Der Spieleprogrammierer :: Community Magazin
Merlin - A Legend awakes :: You are a dedicated C++ (DirectX) programmer and you have ability to work in a team? Contact us!
Siedler II.5 RttR :: The old settlers-style is comming back!

Also known as (D)Evil

Werbeanzeige