#1 17.06.2012 21:23:51

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Objekt-orientierte Programmierung

Hallo!

Ich programmiere hobbtechnisch relativ viel und bisher habe ich es vermeiden können, objekt-orientiert zu programmieren. Records und Arrays waren meine besten Freunde. Bei meinem derzeitigen Asteroids-Projekt stoße ich allerdings an die Grenzen dieses Konzeptes; die Fallunterscheidungen und Coderedundanzen nehmen einfach überhand.

Deshalb will ich auf Objekte umstellen, bin, was das angeht, aber ein absoluter Noob. Die Konzepte hab ich glaub ich verstanden, trotzdem ein paar Fragen:

1. Bei mir wird ein bestimmtes Objekt (der Asteroid) nicht allzuoft erstellt, aber wenn, dann muss es schnell gehen. Seine Struktur ist in einem Array aus 32768 Bytes gespeichert, die werden ja sicher beim Instanziieren alle auf 0 gesetzt. Dauert das lange? Sollte ich das vorher machen - auch wenn das die Dynamikidee zerschießt?

2. Wie speichere ich meine Objekte für den Zugriff? Als TList?

3. Vererbung: Ich habe zwei Klassen, die sich sehr ähnlich sind. Klasse A hat Eigenschaften, die B nicht hat und B hat welche, die A nicht hat. Wie strukturiere ich die jetzt? Eine Mutterklasse, die die Schnittmenge aus den Eigenschaften hat und dann jeweils ableiten?

4. Viele meiner Objekte bekämen eine Render-Methode. Jetzt ist es nur so, dass es recht ineffektiv ist, jedes Objekt einzeln für sich zu rendern. Zum Beispiel werden für jedes Objekt zwei Shader benötigt. Früher hab ich den 1. Shader gesetzt, dann die Hälfte aller Objekte gerendert, dann den 2. gesetzt und die 2. Hälfte aller Objekte gerendert.
Im OO-Fall würde ich sehr viel öfter Shader oder Vertexbuffer oder Renderstates wechseln.

5. Wie macht ihr das eigentlich mit den Vertexbuffern (VB)? Nutze ich nur einen einzigen VB, dann muss ich ja die Speicherverwaltung desselben selbst schreiben (das hab ich bisher getan, klappt auch). Oder gebt ihr jedem Objekt seinen eigenen VB?

6. Design-Entscheidung: Was mache ich z.B. mit meinen Kollisionen: Sagen wir, ich habe Asteroiden und Raumschiffe (also zwei Klassen). In welche Klasse packe ich die Methode, ob Asteroid und Raumschiff kollidieren?

Vielen Dank!

DerPeer

Offline

 

#2 18.06.2012 07:51:02

Lotipats
UltraMember
Registriert: 17.05.2005
Beiträge: 395

Re: Objekt-orientierte Programmierung

Ich kann das ganze nur aus meiner Sicht beantworten, ich weiß nicht, ob es überhaupt allgemeingültige Antworten gibt. Andere Meinungen wäre da vllt. auch hilfreich.

1)
http://stackoverflow.com/questions/1327 … by-default werden die Daten mit 0 initialisiert. Allerdings solltest du auch beachten, dass schon vor Jahren die Geschwindigkeit von RAM im GB/s-Bereich war, schon der >10 Jahre alte langsamste DDR-Anschluss war bei 1,6GB/s. Dein Speicherbereich ist zwei Größenordnungen kleiner, eine Initialisierung sollte deutlich unter 0,1 ms betragen.
Sollte das dennoch zu langsam sein, könntest du eine Vorbelegung in Betracht ziehen, siehe 2.

2)
Ich denke, hier ist entscheidend, das du gerne bezwecken möchtest, bzw. was deine Anforderungen sind.
Ich persönlich nutze meistens "dynamisch wachsende vorinitialisierte Arrays" (k.A., ob es "den" Begriff gibt, ich habe einfach versucht, dem einem Namen zu geben). Ich speichere meine Daten in einem Array, welches bereits mit leerem Platz vorbelegt ist. Wenn ein neues Element dazu kommt, wird es einfach hinten in das nächst freie Feld gepackt (Variable Count sagt mit Inhalt und array[Count] bei 0 basierend ist frei wink). Sollte kein Platz mehr sein (Size==Count), wird das Array vergrößert, nicht nur um 1 sondern gleich um 100, 1000 je nach dem. Sollte Löschen notwendig sein, wird einfach ein Deleted Feld eingefügt und der Wert nur als solches markiert. Bei der nächsten pot. Vergrößerung wird dann aufgeräumt.
Vorteil ist, dass es schneller zugriff möglich ist und bei fortlaufender Benutzung auch eine Cacheoptimierung stattfindet (hängt teilweise vom Inhalt ab, bei Objekten weniger, da es intern nur Zeiger sind). Nachteil ist natürlich die Vergrößerung des Arrays, da hier ein neues (größeres) Array angelegt wird und der Inhalt rüber kopiert.
Um diesen Vergrößerungsnachteil zu minimieren sollte bei der Anfangsinitialisierung ein möglichst großer Wert genommen werden - in Visual Studio Heap-Dokumentation stand mal was von 80-20, also so initialisieren, dass es in 80% der Fälle reicht.

Ich nehme an, "Löschen" ist bei dir sehr oft der Fall und ein relativ aufwändige Vergrößerungs- oder Säuberungsaktion ist für dich nicht so toll. Ich vermute, TList ist für dich besser geeignet, allerdings weiß ich nicht genau, was du damit bezweckst und auch habe ich auf die Schnelle keine Doku von TList gefunden und nehme anhand des Namens nur best. Eigenschaften an. Obiges Konzept des Speichervorbelegens ließe sich auch hier, leicht abgewandelt, anwenden.

3)
Das klinkt für mich sehr danach, dass du eine Klasse Elter anlegst, die die Gemeinsamkeiten von A und B beinhalten und dann A und B von Elter ableitest, also genau so, wie du bereits geschrieben hast.

4)
Ich weiß nicht, ob Delphi mittlerweile "friend" kennt - damit gibt man bei der Deklaration an, dass eine andere Klasse/Methode auf die privaten Elemente zugreifen darf. Das nur als ein möglicher Ansatzpunkt.
Alternativ könntest du deine Objekte nach den Shadern sortieren (vllt. auch extra gespeichert) und dann beim Rendern gehst du diese entsprechend durch. Den Zustand des Rendersystems (aktive Shader, ...) hat ein globales RenderObjekt. Bei diesem fragen deine Objekte den Status ab und setzen falls nötig den neuen Shader (oder das macht die Setzen-Methode des RenderObjektes automatisch). Dadurch solltest du ähnliche Ergebnisse wie im Fall ohne OOP erreichen.

5)
Da muss ich mich mangels eigener Erfahrung heraus halten.

6)
Auch hier fehlt mir die praktische Erfahrung im konkreten Fall, eine Meinung habe ich aber dennoch. tongue
Ich würde im Allg. eine Klasse für alle kollisionsfähigen Objekte habe und dann vererben.
Im konkreten Fall denke ich, ist ein
if(ship.collide(asteroids))then
  ship.removeLife();
end;
womöglich besser.

LOTIPATS

Beitrag geändert von Lotipats (18.06.2012 08:03:01)

Offline

 

#3 18.06.2012 09:52:21

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

1. Okay, dann nehme ich einfach an, dass das schnell genug geht. 32KB ist ja auch nicht gerade viel ;-)

2. Also ich laufe normalerweise alle Objekte durch, und tue dann was mit denen. Also brauche ich weder Suche, noch wahlfreien Zugriff, noch Sortierungen. Aber schnelles Anhängen (egal wo) und Löschen (Position bekannt). Vielleicht ist die einfach/doppelt verkettete Liste meine Wahl?

3. Alles klar. Dann muss ich mir nur kluge Namen einfallen lassen. Mit "a" und "b" komme ich wohl diesmal nicht weit ;-)

4. Ich hab mich blöd ausgedrückt. Für jedes Objekt gilt: es wird mit zwei Rendercalls gerendert. Jeder Rendercall hat seinen Shader. Bei 100 Objekten macht das also 200 Rendercalls, und min. 2mal muss ein Shader gesetzt werden (nämlich dann, wenn ich von allen 100 Objekten jeweils den 1. Rendercall mache und dann von allen 100 Objekten jeweils den 2. Rendercall mache).
Rendere ich nun aber die Objekte komplett nacheinander ab, muss ich 200mal den Shader setzen.

4a. Zu diesem ominösen RenderObjekt: Sehe ich das richtig, dass es Statusvariablen über den gesetzten Shader, Alphablending u.s.w. hat? Und dann tut es die ganze Zeit folgendes?:
if wirdverlangt(Shader1) and not(bereitsgesetzt(Shader1)) then setze(Shader1);
if wirdverlangt(Alphablending) and not(bereitsgesetzt(Alphablending)) then setze (Alphablending);
...

5. Ich finde halt, dass es der Abkapselung/Modularität/Unabhängigkeit eines Objektes zuwiderläuft. Bei der Erstellung muss sich das Objekt jetzt bei einer Speicherverwaltung melden und Speicher reservieren lassen. Und falls das nicht geht, wird es gleich wieder zerstört.
Das heißt, dass eben nicht alle Daten des Objektes im Objekt liegen.

6. Ja, das mit der Überklasse ist ne gute Idee. In DIESER ist dann die Kollision implementiert. Gute Idee!

Vielen Dank!

DerPeer

Beitrag geändert von DerPeer (18.06.2012 09:53:18)

Offline

 

#4 18.06.2012 11:18:23

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

7. Ich habe da noch ein Problem mit "virtual" und "override". Nehmen wir MutterKlasse A, die eine Funktion Foo implementiert hat. In TochterKlasse B, die von A erbt, soll Foo eine andere Funktionalität haben.
"virtual" und "override" habe ich dafür nicht gebraucht. Ich habe einfach Foo nochmals unter dem gleichen Namen in B implementiert, und es scheint zu funktionieren. Wozu brauche ich dann "virtual" und "override"?

DerPeer

Offline

 

#5 18.06.2012 14:53:06

Lotipats
UltraMember
Registriert: 17.05.2005
Beiträge: 395

Re: Objekt-orientierte Programmierung

2) Ja, ich denke eine doppelt verkettete Liste wird für deine Zwecke gut sein.

4)
Bei Multi-Pass-Rendering wäre es vllt. gut, dies auch in den Renderfunktionen abzubilden, also so etwas wie

Code:

for RenderPass := 1 to n do
Begin
  Elements.sortForRenderPass(RenderPass, desc);
  for iPos := Elemets.count-1 downto 0 do
  Begin
    Elements[iPos].doRenderPass(RenderPass);
  end;
end;

Und intern wird bei doRenderPass entsprechend der Zustand gesetzt et cetera.

4.a)
So in der Art, ja. Ich hätte eher Funktionen wie "setShader" gemacht, die dann intern Abfragen wie
if(newShader <> activeShader) then ...
vornehmen.
Im Prinzip könntest du alles, was direkt mit Rendering zu tun hat in diese Klasse packen. Die anderen Objekte rufen dann zum Ändern und Rendern Funktionen dieses RenderObjektes auf. So hättest du eine bessere Modularität und könntest sogar deine Render-Anbindung ändern.
Beispielsweise könntest du dann eine abstrakte Klasse wie TRenderSystem haben und davon abgeleitet sind TDirect3D9, TOpenGL2, ...

7)
Stell dir das Beispiel mit dem TRenderSystem vor. Deine zu rendernden Objekte haben eine Instanz einer von TRenderSystem abgeleiteten Klasse bekommen. Deine Objekte wissen aber nur, dass es ein TRenderSystem ist, nicht wie genau es aussieht. Genau das wäre dann ja der Vorteil, du musst deinen Code an sich nicht ändern und kannst sogar zur Laufzeit (z.B. durch den Benutzer) festlegen, ob Direct3D 9 oder meinetwegen OpenGL 4 genutzt wird.
Ohne virtual und override hast du allerdings ein Problem. Weder der Compiler noch der Linker wissen, was für ein TRenderSystem  du nun verwendest. Sie können also nur gegen die Funktionen von TRenderSystem bauen bzw. linken. Mit virtual sagst du, dass die Methode durch eine abgeleitete Klasse überschrieben werden könnte, mit override sagst du in der abgeleiteten Klasse, dass du dies auch wirklich tun willst. Da ich in Delphi schon etwas eingerostet bin, möge man mich korrigieren, falls die Wörter etwas leicht anderes bedeuten (der Kern sollte aber stimmen).

Vllt. ein kleines Beispiel (ich muss jetzt einfach code-Blöcke nehmen):

Code: delphi

type

trendersystem = class
  public
    procedure renderv1; virtual;
    procedure renderv2; virtual;
    procedure renderv3;
    procedure renderv4;
end;
tmydirect9 = class (trendersystem)
  public
    procedure renderv1;            //Warnung W1010 Methode 'renderV1' verbirgt virtuelle Methode vom Basistyp 'TRenderSystem'
    procedure renderv2; override;
    procedure renderv3;
//    procedure renderV4; override;  //Fehler E2170 Eine nichtvirtuelle Methode kann nicht überschrieben werden
end;

procedure trendersystem.renderv1;
begin
  writeln('TRenderSystem.renderV1');
end;
procedure trendersystem.renderv2;
begin
  writeln('TRenderSystem.renderV2');
end;
procedure trendersystem.renderv3;
begin
  writeln('TRenderSystem.renderV3');
end;
procedure trendersystem.renderv4;
begin
  writeln('TRenderSystem.renderV4');
end;

procedure tmydirect9.renderv1;
begin
  writeln('TMyDirect9.renderV1');
end;
procedure tmydirect9.renderv2;
begin
  writeln('TMyDirect9.renderV2');
end;
procedure tmydirect9.renderv3;
begin
  writeln('TMyDirect9.renderV3');
end;
//procedure TMyDirect9.renderV4;
//Begin
//  Writeln('TMyDirect9.renderV4');
//end;

var
  trenderer : trendersystem;
begin

  trenderer := tmydirect9.create();

  trenderer.renderv1();
  trenderer.renderv2();
  trenderer.renderv3();
  trenderer.renderv4();

  writeln('');

  tmydirect9(trenderer).renderv1();
  tmydirect9(trenderer).renderv2();
  tmydirect9(trenderer).renderv3();
  tmydirect9(trenderer).renderv4();
end.

Ausgabe (getestet):

Code:

TRenderSystem.renderV1
TMyDirect9.renderV2
TRenderSystem.renderV3
TRenderSystem.renderV4

TMyDirect9.renderV1
TMyDirect9.renderV2
TMyDirect9.renderV3
TRenderSystem.renderV4

TMyDirect9.renderV4 ist auskommentiert, da es, wie erwartet, einen Fehler wirft. Du kannst eben nur virtuelle Methoden überschreiben.
An der Ausgabe siehst du auch, dass du, obwohl du TMyDirect9 als Instanz hast, bei Übergabe als TRenderSystem auch nur die Funktionen von TRenderSystem nutzt, außer du hast virtual und override verwendet.

LOTIPATS

Beitrag geändert von Lotipats (18.06.2012 14:58:19)

Offline

 

#6 18.06.2012 16:55:59

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

Gibts denn eine doppelt verkettete Liste in Delphi schon? Oder muss ich das selber machen?

Dein Multipass-Ansatz ist in Ordnung, einfach die Rendermethode quasi in 2 Teile aufspalten.

Danke, dass du dir so viel Mühe gemacht hast. Ich glaube, ich habs jetzt verstanden. Das Problem entsteht, wenn ich ein Tochterobjekt an eine Methode übergebe, deren Typ die Mutterklasse ist. Dann würde normalerweise in der Methode der Implementierung der Mutterklasse ausgeführt, und nicht die der Tochterklasse.
Schon kompliziert.

DerPeer

Offline

 

#7 19.06.2012 07:41:41

Lotipats
UltraMember
Registriert: 17.05.2005
Beiträge: 395

Re: Objekt-orientierte Programmierung

Ich habe eben mal im Quelltext nachgesehen. Zumindest bei Delphi 2009 basiert TList auf ein Feld von Zeigern und Klassen wie TStack und TQueue nutzen TList. Mir fällt deshalb nicht einmal eine Klasse ein, die eine einfach verkettete Liste nutzt bzw. darstellt.
Aber vllt. bin ich dafür auch der falsche Ansprechpartner, da ich die letzten Jahren leider nur Assembler, C und C++ programmiert habe.

Offline

 

#8 19.06.2012 09:34:25

DragonFlyOfGold
ProMember
Ort: Berlin
Registriert: 09.11.2005
Beiträge: 139

Re: Objekt-orientierte Programmierung

Wegen den verketteten Listen: Nutze doch einfach ne ganze normale Liste, dadurch ersparst du dir viel mühe beim Programmieren der ganzen Funktionalitäten. Und wenn du die Wahl hast nutze immer generische Listen (ab Delphi 2009), die machen das Leben soviel einfacher. Und zum Thema Schnelligkeit: Wenn du Elemente hinzufügst geht das sehr schnell, da sie immer hinter eingefügt werden. Außerdem wird das Array zum Speichern intelligent bei Größeränderung verwaltet (nicht nur um eins die Größe erhöhen, sondern verdoppeln und beim Entfernen wird nicht gleich das Array wieder verkleinert). Um das Löschen effizent zu halten, kannst du immer das zu löschende und das letzte Element vertauschen und dann das Letzte löschen, so müssen keine Daten verschoben werden und die Reihenfolge ist dir ja eh egal.
Daher mein Tipp an dich: Nutze Code den es schon gibt, dadurch ersparst du dir ne Menge Arbeit und viel zeit;)

Grüße DfoG

Beitrag geändert von DragonFlyOfGold (19.06.2012 09:37:43)

Offline

 

#9 19.06.2012 14:35:36

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

@Dragon: Hab zwar meine doppelt verkettete Liste implementiert, aber so ganz vertrau ich dem nicht ;-)

Vielleicht nehme ich wirklich einfach TList, da weiß ich, dass es sicher funktioniert.

Noch eine Frage: wie weit wirkt "virtual"? Bis in die zwanzigste Vererbung? Also konkret:

mutter = class
procedure foo; virtual;
end;

tochter = class(mutter);
procedure foo; override; (hier auch virtual, um weiteres Überschreiben zu erlauben?)
end;

enkelin = class(tochter);
procedure foo; override;
end;

Offline

 

#10 19.06.2012 15:05:18

DragonFlyOfGold
ProMember
Ort: Berlin
Registriert: 09.11.2005
Beiträge: 139

Re: Objekt-orientierte Programmierung

Virtual "wirkt" über die komplette Vererbung hinweg, egal in welche Tiefe du dich befindest. Außerdem wäre es auch nicht möglich die Methode erneut als virtual zu markieren UND sie mit override zu überschreiben.

Wenn du dir nicht sicher bist ob deine Implementierung was taugt, schreib UnitTests dafür (Stichwort DUnit), damit kommt man meist schon ein gutes Stück weiter.

Offline

 

#11 19.06.2012 15:34:06

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

Danke!

Noch eine dumme Frage: Der Konstruktor wird auch ganz normal weitervererbt, oder?

mutter = class
contructor create;
end;

tochter = class(mutter);
end;

enkelin = class(tochter);
constructor create;
end;

implementation
constructor enkelin.create;
begin
  inherited;
end;

Das "inherited" führt dasjenige create aus, das an die tochter vererbt wurde, also ursprünglich aus der mutter stammt, richtig?

Was würde passieren, wenn wir noch schrieben:

urenkelin = class(enkelin);
end;

und dann urenkelin.create aufrufen? Es würde dasselbe passieren wir bei enkelin.create?

Ich finde das alles gar nicht so einfach ;-)

DerPeer

Offline

 

#12 22.06.2012 06:58:34

Lotipats
UltraMember
Registriert: 17.05.2005
Beiträge: 395

Re: Objekt-orientierte Programmierung

Versuchen wir es doch einfach mal:

Code: delphi

type
mutter = class
  public
   constructor create;
end;
tochter = class(mutter)
end;
enkelin = class(tochter)
  public
    constructor create;
end;
urenkelin = class(enkelin)
end;

constructor mutter.create;
begin
  writeln('constructor mutter.create');
  inherited;
end;
constructor enkelin.create;
begin
  writeln('constructor enkelin.create');
  inherited;
end;

var
  turenkelin: urenkelin;
begin
  turenkelin := urenkelin.create();

  readln;
end.

Das Ergebnis:

Code:

constructor enkelin.create
constructor mutter.create

Da bei urenkelin kein Konstruktor angegeben ist, wird automatisch der von enkelin genommen, so wie es auch bei anderen Funktionen ist. Durch das inherited gibst du an den nächst höheren Konstruktor, den von mutter, weiter. Dieser gibt dann an den von TObject ab.
Dadurch, dass TObject immer am Ende der Vererbungskette ist, ist auch garantiert, dass jede Klasse einen funktionierenden Konstruktor hat. wink

LOTIPATS

Beitrag geändert von Lotipats (22.06.2012 06:59:29)

Offline

 

#13 22.06.2012 12:55:16

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

Cool! Herzlichen Dank für deinen Code! Das hab ich jetzt verstanden, denke ich ;-)

DerPeer

Offline

 

#14 24.06.2012 19:20:27

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

Noch eine Frage hätte ich:

Bei der Objekt-Instanziierung wird "draußen" geschrieben: myObject = Tobject.create;

Das heißt ja, "create" wird aufgerufen und ein Zeiger auf das Objekt wird zurückgegeben (an myObject).

Was passiert, wenn aus irgendwelchen Gründen das Objekt nicht erstellt werden konnte? Delphi-Hilfe sagt: Wenn eine Exception währenddessen geworfen wird, dann wird sogleich der Destruktor aufgerufen. Wird dann "nil" zurückgegeben?

Wie muss ich damit umgehen, wenn ich im Konstruktor einige Tests tun muss und dabei feststelle, dass das Objekt nicht erzeugt werden kann (z.B. weil irgendwelche Umstände gerade nicht passen) ? Muss ich manuell eine Exception werfen? Kann ich manuell "nil" zurückgeben? Wie gebe ich die Info, dass es nicht geklappt hat, nach "draußen"?

Vielen Dank nochmal!

DerPeer

Offline

 

#15 25.06.2012 18:45:48

Lotipats
UltraMember
Registriert: 17.05.2005
Beiträge: 395

Re: Objekt-orientierte Programmierung

Ich bin mir nicht sicher, ob ich deine Fragen richtig verstanden habe.

Schauen wir uns folgendes Beispiel an:

Code: delphi

type

tmytest = class
  public
    constructor create;
end;

constructor tmytest.create;
begin
  raise exception.create('AHHHH, ein Fehler!');
end;

var
  ttest : tmytest;
begin
  try
    try
      ttest := tmytest.create();
    except
      on e: exception do writeln(e.tostring);
    end;
  finally
    writeln('Pointer: '+uinttostr(cardinal(ttest)));
  end;

  readln;
end.

Die dazu passende Ausgabe ist:

Code:

AHHHH, ein Fehler!
Pointer: 0

Wenn die Zeile mir raise auskommentiert wird, erscheint folgende Ausgabe:

Code:

Pointer: 31824352

Deine Vermutung, dass der Wert nil ist, war also richtig. Da nur du als Klassenentwickler weißt, wann eine Instanz dieser Klasse nicht erstellt werden kann, obliegt es auch dir als Entwickler die Tests durchzuführen und notfalls "die Notbremse" zu ziehen. wink
Wobei ich aber auch anmerken muss, dass ich dies bisher selber noch nicht gemacht habe. Rückblickend wäre es 1 oder 2 mal vllt. gar nicht schlecht gewesen. Danke für diese Information!

LOTIPATS

PS: Es fällt mir ja jetzt erst auf, dass der Code-Darsteller nicht nur einige Dinge fett macht, sondern auch alles in Kleinbuchstaben darstellt. Da gehen ja jegliche Kodekonventionen flöten. mad

Offline

 

#16 25.06.2012 19:13:19

Gnietschow
ProMember
Ort: Berlin
Registriert: 20.06.2007
Beiträge: 237

Re: Objekt-orientierte Programmierung

Hab in der Delphi-Hilfe auch diesen Satz dazu gefunden:

Zitat:

Wenn eine Exception in einem Konstruktor ausgelöst wird, dann gibt dieser Konstruktor nil zurück.

Gut zu wissen smile

MfGnietschow


Es gibt 10 Gruppen von Menschen - die die das Binärsystem verstehen und die anderen.  :-)
Vegetarier essen meinem Essen das Essen weg ;)
-------------------------------------------------------------------------------------------------------------------
Der Community-Hub für Videospiele: gameloop.io

Offline

 

#17 27.06.2012 07:04:58

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

Vielen Dank! Das hätte ich aber wirklich auch mal selber ausprobieren können!

Dann weiß ich ja bescheid: will ich im Konstruktor das Erstellen des Objektes abbrechen/rückgängig machen, dann raise ich eine Exception. Und draußen weiß ich bescheid, da ich mit try und except arbeite.
Ich könnte natürlich auch (wie Gnietschow bestätigt hat) auf nil abfragen, aber die Exception muss ich ja trotzdem behandeln.

Themenwechsel.

Seit ich jetzt auf OO umstelle, scheint irgendwie alles komplizierter zu werden. Z.B. hatte ich früher ein Array of Records, die ein "active: boolean"-Property hatten.
Jetzt hab ich das Problem, wenn ich alle Objekte in meiner TList durchgehen will, und es passieren kann, dass bei diesem Durchgehen Objekte gelöscht werden. Dann würde ich bei einer For-Schleife nämlich Objekte überspringen.
Das ist zwar schon irgendwie lösbar, aber ich dachte immer, dass OO nicht MEHR Probleme aufwerfen würde. Naja. Also vielleicht so:

i:=0;
while (i<Liste.Größe) do begin
  if (tobject)Liste[i].doSomethingAndTellMeIfThisObjectIsStillThere then inc(i);
end;

Wie auch immer jetzt die Syntax wäre. Habt/hattet ihr ähnliche Probleme?

Eine kurze Frage noch: Ich habe bei meinem Raumschiff Mündungsfeuer. Also ein kurzer Lichtblitz, der auftritt, wenn geschossen wird. Dieser Lichtblitz ist also nur einige Frames zu sehen. Macht es trotzdem Sinn, das komplett als Objekte zu designen bzw. diese Objekte ständig zu createn und ein, zwei Frames später wieder zu destroyen? Vielleicht unterschätze ich ja Delphi, aber mit kommt das immer wie unnütze Arbeit vor (create und destroy), aber bestimmt habe ich da eine falsche Vorstellung.

Danke euch nochmal!

DerPeer

Offline

 

#18 27.06.2012 09:27:18

DragonFlyOfGold
ProMember
Ort: Berlin
Registriert: 09.11.2005
Beiträge: 139

Re: Objekt-orientierte Programmierung

Ich kenne dieses Problem auch sehr gut, dass man eine Liste mit einer for - Schleife durchgeht und Probleme bekommt, wenn darin Objekte gelöscht werden. Die Lösung: nimm eine for downto Schleife:

Code: delphi

for i := liste.gre - 1 downto 0 do
begin
  liste[i].dosomething;
  if not liste[i].alive then liste.delete(i);
end;


Delphi ist sehr effizent was das Erstellen und Freigeben von Objekten betrifft. Einzig wenn diese Objekte wirklich sehr groß sind, sollte man nicht jeden Frame davon ein paar Tausend erstellen und löschen. Schlimmer wird es, wenn du immer Texturen oder andere Ressourcen von der Festplatte laden musst. In diesen Fällen würde ich dir raten, einen Ressourcenpool zu bauen. Dieser lädt bei der ersten Verwendung die Ressource und gibt danach nur noch Referenzen darauf zurück. Erst beim Verlassen des Programmes oder beenden des Levels werden diese wieder Freigegeben.
Ansonsten könntest du für dein Problem auch das Mündungsfeuerobjekt am Leben lassen und nur einen Schlater im Objekt auf AUS stellen, sobald es erlischt und beim nächsten Schuss halt wieder auf AN etc.

Grüße DfoG

Offline

 

#19 27.06.2012 09:40:42

Lotipats
UltraMember
Registriert: 17.05.2005
Beiträge: 395

Re: Objekt-orientierte Programmierung

Ich denke, dass ich eine Sache der Ansicht und vor allem der Gewöhnung.
Immer wenn ich gezwungen bin C-Code zu schreiben, komme ich (mehrfach) zu dem Punkt an dem ich mir denke "jetzt wäre eine Klasse dafür super". Mein Code sieht dann vermutlich so aus, dass die einzelnen c-Dateien nahezu Klassen sind, nur dass es kein public, ... gibt und "class ..." fehlt. hmm

Objektorientierung bedeutet auch immer Overhead, sowohl bei der Programmierung, als auch der Ausführung. Wenn die Projekte größer werden kann es Arbeit (Programmierung, Wartung, ...) sparen.
Um die Vorteile von OOP nutzen zu können, muss man es allerdings auch "richtig" nutzen. Wenn man alles so macht wie immer, nur "class" davor schreibt, dann passiert es leicht, dass es nur Overhead produziert.

Ich denke mal, dass dein Problem ist, dass du einfach noch nicht richtig für OOP denkst, was verständlich ist, wenn man es bisher immer nur andere Lösung verwendet hat. U.U. wären hier OOP Tutorials gut (aber explizit für OOP, nicht für eine Sprache mit OOP-Funktionalität).

Zitat:

Eine kurze Frage noch: Ich habe bei meinem Raumschiff Mündungsfeuer. Also ein kurzer Lichtblitz, der auftritt, wenn geschossen wird. Dieser Lichtblitz ist also nur einige Frames zu sehen. Macht es trotzdem Sinn, das komplett als Objekte zu designen bzw. diese Objekte ständig zu createn und ein, zwei Frames später wieder zu destroyen? Vielleicht unterschätze ich ja Delphi, aber mit kommt das immer wie unnütze Arbeit vor (create und destroy), aber bestimmt habe ich da eine falsche Vorstellung.

Ich würde hier vermutlich dem Raumschiff eine Instanz des "Lichtblitz-Objektes" geben. Das Raumschiff entscheidet dann, ob es zu sehen ist oder nicht, evtl. rendert das Raumschiff sogar den Blitz in seiner render-Funktion (ruft render vom Lichtblitz auf). Das hängt aber auch vom restlichen Code ab.

Code:

TFlashLight = class 
//...
end;
TSpaceshit = class
//...
protected
  tFlashLight : TFlashLight;
//...
end;

LOTIPATS

Offline

 

#20 27.06.2012 14:00:46

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

Ach, das mit der runterzählenden Schleife ist mir nicht eingefallen. Super!

@Lotipats: Ja, das ist für mich doch eine große Umstellung. Ich habe z.B. gemerkt, dass einfach-losprogrammieren nicht mehr funktioniert. Man MUSS vorher planen.

Die Idee, die Mündungsfeuerobjekte zunächst alle zu erstellen, und dann im Frame zu entscheiden, ob sie gerendert werden, hatte ich eben auch. Wusste nur nicht, ob das noch OO ist ;-)

DerPeer

Offline

 

#21 28.06.2012 05:40:06

Lotipats
UltraMember
Registriert: 17.05.2005
Beiträge: 395

Re: Objekt-orientierte Programmierung

[quote=DerPeer]Die Idee, die Mündungsfeuerobjekte zunächst alle zu erstellen, und dann im Frame zu entscheiden, ob sie gerendert werden, hatte ich eben auch. Wusste nur nicht, ob das noch OO ist ;-)

Wieso sollte das kein OOP sein?
Die Komponenten bei Delphi haben ja auch "visible" und "enable". Auch wenn beide ausgestellt ist, ist das Objekt noch da, nur kann es vom Benutzer weder gesehen noch benutzt werden. wink

LOTIPATS

Offline

 

#22 29.06.2012 21:45:32

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

Ich habe gerade festgestellt, dass man destroy oder free nicht im Konstruktor aufrufen sollte. Der Konstruktor gibt nämlich trotzdem einen vernünftigen Pointer (nicht nil) zurück.
Auf diese Referenz kann man aber nicht zugreifen. Ein free danach führt zu einer Access Violation. Wahrscheinlich wurde das Objekt ordnungsgemäß freigegeben, aber der Rückgabewert des Konstruktor ist dann veraltet.
Von außen kann dann also nicht mehr festgestellt werden, ob das Objekt tatsächlich da ist oder nicht - stimmts?

Ich überlege, ob ich der create-Methode einen Ausgabeparameter "hatGeklappt" mitgebe. Das ganze über Exceptions zu lösen finde ich irgendwie nicht elegant. Ich bin mir nur nicht sicher, ob es sauber ist, free im Konstruktor aufzurufen.

Danke

DerPeer

Offline

 

#23 30.06.2012 06:02:29

Lotipats
UltraMember
Registriert: 17.05.2005
Beiträge: 395

Re: Objekt-orientierte Programmierung

Nach http://www.delphi-treff.de/object-pasca … estruktor/ Webseite wird der Konstruktor von Deplhi noch verändert.

Zitat:

[...]
Im Gegensatz zu normalen Methoden generiert der Compiler Code, der dem Konstruktor zwei versteckte Parameter übergibt. Der Typ des ersten Parameters "Self" hängt dabei vom Wert des Zweiten ab. Dieser wird im CPU Register DL übergeben und gibt den Aufrufmodus des Konstruktors an.
[...]
Der erste Aufrufmodus ist "ClassCreate with Allocation" und wird beim Erzeugen einer Instanz der Klasse genutzt.
[...]
Dies führt dazu, dass als erstes die System-Funktion _ClassCreate, mit dem ClassType Self-Parameter, vom Konstruktor aus aufgerufen wird. Diese ruft ihrerseits die Klasssenmethode NewInstance auf, die den notwendigen Speicher reserviert und die Methode InitInstance aufruft.
[...]
_ClassCreate richtet des Weiteren einen Exception-Block ein, der bei einer Exception innerhalb des Konstruktor-Codes automatisch den Destruktor aufgeruft. Dieser Exception-Block endet mit dem Verlassen des äußersten Konstruktors. Als Rückgabewert liefert_ClassCreate den mit NewInstance erzeugten Instanz-Zeiger, auch bekannt als Self, der nun im Konstruktor wie bei einer normalen Methode Verwendung findet.

Es besteht für mich deshalb die Vermutung, dass der Konstruktor zu einer Art Funktion mutiert, deren Rückgabewert der Zeiger ist (weshalb "Variable := Klasse.Create" auch die Schreibweise ist). Das Problem dabei ist, dass du vermutlich, außer mit einer Exception, keinen Einfluss auf "Result" des Konstruktors hast.
Aber wie gesagt, dass ist nur eine Vermutung.

Zitat:

Von außen kann dann also nicht mehr festgestellt werden, ob das Objekt tatsächlich da ist oder nicht - stimmts?

Das kann ich dir nicht beantworten. Ich denke, der Knackpunkt ist die Frage, ob du herausfinden kannst, ob die Speicheradresse alloziiert ist oder nicht.


LOTIPATS

Offline

 

#24 02.07.2012 18:19:03

DerPeer
GodlikeMember
Ort: Berlin
Registriert: 04.02.2005
Beiträge: 1291

Re: Objekt-orientierte Programmierung

Ich mal wieder.

Ich habe eine Klasse A, in der ich ein record als protected deklariert hab. Nun erbt Klasse B von A, C von B und D von C. Naja, jedenfalls erbt D alles von A. Aber mein protected record ist in D nicht ansprechbar. Erst, wenn ich es public mache.
Habe ich was falsch gemacht/falsch verstanden?

Die beiden Klassen liegen in unterschiedlichen Units.

Danke!

DerPeer

Offline

 

#25 02.07.2012 21:39:13

Gnietschow
ProMember
Ort: Berlin
Registriert: 20.06.2007
Beiträge: 237

Re: Objekt-orientierte Programmierung

Hmm eigentlich müsste es gehen mit protected. Private würde ja das verhindern. Ich benutz häufiger protected Sachen und greif dann in anderen Units per Ableitung darauf zu. Vll kannst du ja mal den Code dazu posten?

MfGnietschow


Es gibt 10 Gruppen von Menschen - die die das Binärsystem verstehen und die anderen.  :-)
Vegetarier essen meinem Essen das Essen weg ;)
-------------------------------------------------------------------------------------------------------------------
Der Community-Hub für Videospiele: gameloop.io

Offline

 

Brett Fußzeile

Powered by PunBB
© Copyright 2002–2005 Rickard Andersson