PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Frage @ zeckensack


Che
2003-03-24, 20:16:02
Ich sehe mir gerade deinen Brotbäcker (geniales Programm, Gratulation!) an und stoße auf folgendes:

void operator += (const ExtremeFloat& rhs);
ExtremeFloat operator + (const ExtremeFloat& rhs) const;

ad 1: Warum in aller Welt const UND Referenz? const verwende ich doch um einen Parameter aus Versehen NICHT zu ändern, und eine Referenz verwende ich UM einen Parameter zu ändern. (geht ja normal mit call-by-value nicht) Beides zusammen erscheint mir ziemlich kontraproduktiv...

ad 2: Was hat das abschließende const in der Deklaration von operator + zu bedeuten?

Mfg

liquid
2003-03-24, 20:28:51
Er macht das wohl so, damit Speicher gespart wird, besonders was das Kopieren von Objekten angeht. Eine Referenz wird ja intern vom Compiler als Pointer gehandhabt und ist deshalb auch 32-Bit lang.
Übergibst du eine konstante Referenz so sind das in jedem Fall nur 32-Bit an Daten, die kopiert werden müssen. Sonst (also Call-By-Value) müsste ja das gesamte ExtremeFloat Objekt vom Copy-Konstruktor kopiert werden und das ist mit absoluter Sicherheit größer als 32-Bit, die der interne Pointer fressen würde.
Und das const zusammen mit der Referenz halt aus dem Grunde, dass man die Referenz ja nicht ändern soll. Wenn ich was zu einem ExtremeFloat dazuzählen, dann will ich den Teil den ich effektiv dazupacke ja nicht ändern.

Zu 2) Das const dahinter der Deklaration gibt an, dass in dieser Klassenmethode keine Daten verändert werden sollen bzw. verspricht dem Compiler dieses. Das ist einmal gut zur Fehlersuche, wenn man wieder mal nicht == sondern nur = getippt hat und dann natürlich für den Compiler zur Optimierung.
In const Methoden kann man dann auch nur const-Methoden der Klassenmember aufrufen, das nur so am Rande.

Alles richtig so zecki?

cya
liquid

micki
2003-03-24, 22:45:08
1+ @ liquit

:D

MfG
micki

zeckensack
2003-03-25, 03:31:17
Alles richtig @ liquid :)

Noch ergänzend, const class& ist für C++ die bevorzugte Deklaration für solche Operatoren. Dadurch wird erstens solcher Code möglich
ExtremeFloat a,b,c;
c=a+b; //look mom, no pointers!
, zweitens eben der eingebaute 'sanity check' const, der logische Fehler im Code zu vermeiden hilft.

Noch ad 2)
dieser Operator ist als Klassenmember deklariert. Es gibt also zwei Operanden, einmal *this (implizit, da Klassenmember), einmal rhs (right hand side) aus der Parameterliste. Der Copy-Konstruktor 'will' übrigens die gleiche Deklaration sehen, insofern habe ich mich lediglich an allgemeinen Konventionen von C++ orientiert.

Weiter ad 2)
Die beiden Operanden dürfen von einer sauberen Addition nicht verändert werden, ergo will ich beide const deklarieren. Ich gebe ein neues ExtremeFloat zurück, dies ist das einzige auf das ich sinnvollerweise schreibend zugreifen muß.
Das const hinter der Parameterliste modifiziert nun den this-Pointer. Anstatt 'ExtremeFloat* this' muß man ihn sich nun als 'const ExtremeFloat* this' deklariert vorstellen - im Scope der Implementation des Operators.

Che
2003-03-25, 18:42:01
Ahhh ja, tolle Sache dieses C++ Teufelszeug... :D

Und dann hab ich noch was gefunden :)

ExtremeFloat tmp(*this);

Anm.: aus einer Methode von ExtremeFloat

Wenn ich alles richtig verstehe deklarierst du da einen ExtremeFloat und übergibst dem Konstruktor einen dereferenzierten Pointer auf einen ExtremeFloat = einen ExtremeFloat. Einmal abgesehen davon dass ich nirgends einen entsprechenden Konstruktor gefunden habe (da hab ich dann wohl was übersehen ;)) frage ich mich ob das überhaupt erlaubt ist. Man kann doch einem Konstruktor nicht als Parameter ebenjenes Objekt übergeben, das er konstruieren soll...oder doch ?

btw. was ist der Copy Konstruktor?

MfG

liquid
2003-03-25, 19:03:06
Also ich mutmaße mal wieder ein wenig (wenn ich darf *g*).

Dieser Aufruf findet mit Sicherheit in einer Klassenmethode der ExtremeFloat class statt.
Dort wird er benutzt (soweit ich das sehen kann), um von sich selbst eine Kopie anzufertigen. Also die Klasse auf die die Klassenmethode aufgerufen wird, legt von sich selbst eine Kopie an.

Der entsprechende Konstruktur müsste folgendermaßen deklariert worden sein (ich habe den Brot Code zwar auch in meinem Code-Archiv, hab mich aber noch net näher mit außeinandergesetzt).
ExtremeFloat(const ExtremeFloat& rhs);

Das ist wieder der besagte Copy-Constructor, der Objekte als Kopien eines anderen gleichartigen Objekts anlegt.
Hier nimmt er einfach den dereferenzierten this-Zeiger der Klassenmethode auf und erzeugt eine Kopie der Daten auf dieser this-Zeiger zeigt. Denn den this-Zeiger alleine kann man dem Copy-Constructor nicht übergeben, es muss schon die derefenzierte Version sein. Diese wird anderesseits auch weider als Pseudo-Zeiger übergeben (da ja Call-By-Reference).

Hoffe das stimmt alles so.

cya
liquid

EDIT: Copy-Constructor:
Ist eigentlich ein handelsüblicher Konstruktor, dem als Parameter ein Objekt, der Sorte was er erzeugen soll (also wenn er sein abc-Objekt erzeugen soll, dann muss ihm auch ein abc-Objekt übergeben werden), übergeben wird.
Der Kopiekonstruktor ist immer dann in Aktion, wenn man Call-By-Value durchführt, da dort ja das übergebene Objekt kopiert werden muss. Und da ist es halt sicherer, wenn man eine "tiefe" Kopie abliefern, als eine lasche "flache", die dann Pointerfehler aufwirft.

Xmas
2003-03-25, 22:08:44
Originally posted by Che
Ahhh ja, tolle Sache dieses C++ Teufelszeug... :D

Und dann hab ich noch was gefunden :)

ExtremeFloat tmp(*this);

Anm.: aus einer Methode von ExtremeFloat

Wenn ich alles richtig verstehe deklarierst du da einen ExtremeFloat und übergibst dem Konstruktor einen dereferenzierten Pointer auf einen ExtremeFloat = einen ExtremeFloat. Einmal abgesehen davon dass ich nirgends einen entsprechenden Konstruktor gefunden habe (da hab ich dann wohl was übersehen ;)) frage ich mich ob das überhaupt erlaubt ist. Man kann doch einem Konstruktor nicht als Parameter ebenjenes Objekt übergeben, das er konstruieren soll...oder doch ?

btw. was ist der Copy Konstruktor?

Genau das ist der Copy Constructor. Ein solcher wird übrigens automatisch erzeugt, falls keiner explizit deklariert wird, genauso wie der Default Constructor und Assignment Operator (operator= ). Deswegen findest du diese auch im Code nicht.

Der Copy Constructor macht standardmäßig "Memberwise Initialisation", d.h. für jeden Member wird einzeln der Copy Constructor aufgerufen, natürlich mit dem entsprechenden Member aus der zu kopierenden Klasse als Argument.

zeckensack
2003-03-26, 15:22:42
Noch 'ne Ergänzung zum Copy-Constructor:
class
ExtremeFloat
{
ExtremeFloat(const ExtremeFloat& rhs);
};
Das ist der copy constructor. Oder anders gesagt: wenn man einen solchen haben will, dann muß er nach Standard-C++ exakt so aussehen.
Wenn man das so gemacht hat, kann man ein Objekt direkt als Identität eines anderen erzeugen, und somit den 'normalen' Konstruktor umgehen. Das lohnt sich, wenn dieser nicht-trivial ist. In meinem Fall setzt zB der Default-Constructor das 'ExtremeFloat' auf Null, indem alle 32 Bytes geschrieben werden. Ist zwar nur ein sehr kleiner Overhead, aber immerhin. Wenn diese Operation wie in meinem Fall sehr oft passiert, dann möchte man dies vermeiden können.
________________
Eigentlich sollte ein Compiler bei folgender ZeileExtremeFloat tmp=*this;bereits von sich aus darauf kommen, daß der Copy Constructor gefragt ist. Schließlich erzeuge ich in dieser Zeile ein neues Objekt und weise sofort ein anderes zu. Ein 'Eigenleben' braucht tmp einfach nicht.

MSVC6 SP5 ist dafür allerdings offensichtlich zu blöde, selbst im Release-Build mit voller Optimierung (ich hab's ausgemessen). Es wird zuerst der 'normale' Konstruktor aufgerufen, und danach der assignment operator - der hier:
ExtremeFloat& operator =(const ExtremeFloat& rhs);

Deswegen dieses komische Dings da oben, um den Aufruf des copy constructors zu erzwingen.

Ich kann wirklich nur jedem empfehlen, MinGW/GCC3.2 für Release-Builds zu benutzen, da ist MSVC ein Scheiß dagegen. Der Debugger ist ganz nett, aber der Compiler ... :|

Che
2003-03-26, 17:33:10
Aaaah, also wieder mal alles wegen der ewigen Geißel Geschwindigkeit...

Vielen Dank ihr drei :)

Che
2003-04-10, 18:42:53
Wo wir hier gerade dabei sind die Semantik von C++ auseinanderzunehmen:

Was sind eigentlich Singletons und wozu sind sie da? Ich hab auf einem anderen Board gelesen (dabei gings um Engine-Design) das man sich damit "die häßlichen extern-Konstrukte" sparen kann, und das interessiert mich natürlich schon...;)

MfG

Demirug
2003-04-10, 18:52:40
Als Singletons bezeichnet man Klassen von denen es in einem Process immer nur genau eine Instanz gibt und man auch keine zusätzlichen erzeugen kann.

Um das ganze zu erreichen gibt es ein paar Wege. Was aber alle gemeinsam haben ist das es keinen öffentlichen Konstruktor gibt.

zeckensack
2003-04-10, 19:04:27
Originally posted by Che
Wo wir hier gerade dabei sind die Semantik von C++ auseinanderzunehmen:

Was sind eigentlich Singletons und wozu sind sie da? Ich hab auf einem anderen Board gelesen (dabei gings um Engine-Design) das man sich damit "die häßlichen extern-Konstrukte" sparen kann, und das interessiert mich natürlich schon...;)

MfG
Hmmm :|
Ich nutze 'extern-Konstrukte' eigentlich nur für Singletons (und globale Variablen; Ziel der Übung ist aber globale Variablen durch Singletons zu ersetzen, von daher auch wurscht).

//Cpu.h
#define CPU_CAPS_RDTSC 1
#define CPU_CAPS_MMX 2
#define CPU_CAPS_SCREAMING_SINDIE 4
#define CPU_CAPS_STREAMING_STORES 8
#define CPU_CAPS_3DNOW 16
#define CPU_CAPS_3DNOW_ENHANCED 32
#define CPU_CAPS_SSE2 64

class
Cpu
{
public:
Cpu();
uint caps;
};

//this is a conceptually a singleton, so we'll declare the instance right away
//defined in cpu.cpp
extern Cpu cpu;

//Cpu.cpp
#include "common.h"
#include "x86_feature_detect.ah"
#include "cpu.h"
#include <stdio.h>

Cpu cpu;

Cpu::Cpu()
{
caps=x86_detect_cpu_features();

printf("x86");
if (caps&CPU_CAPS_RDTSC) printf("/RDTSC");
if (caps&CPU_CAPS_MMX) printf("/MMX");
if (caps&CPU_CAPS_3DNOW)
{
printf("/3DNow!");
if (caps&CPU_CAPS_3DNOW_ENHANCED) printf("+");
}
if (caps&CPU_CAPS_SCREAMING_SINDIE) printf("/SSE");
if (caps&CPU_CAPS_SSE2) printf("/SSE2");
printf("\n");
}

Obacht: dies ist Code unter LGPL :naughty:

Demirug
2003-04-10, 19:15:38
Zecki das ist aber kein gutes Singleton

zeckensack
2003-04-10, 19:19:52
Originally posted by Demirug
Zecki das ist aber kein gutes Singleton Das ist ein sehr einfaches Singleton ;)

Ich hätte auch noch diverse 'Factory'-Klassen im Angebot, die sind aber 'geringfügig' länger :D

Der Hauptgrund das so zu machen, ist daß der Konstruktor sofort aufgerufen wird wenn der Prozess startet. Dann muß ich das nicht selber machen ;)

Und nun sag, was stört dich daran? =)

Demirug
2003-04-10, 19:30:54
Originally posted by zeckensack
Das ist ein sehr einfaches Singleton ;)

Ich hätte auch noch diverse 'Factory'-Klassen im Angebot, die sind aber 'geringfügig' länger :D

hehe Factorys habe ich hier auch ein paar. Hüllenklassen (braucht man bei C++ nicht)kann ich auch anbieten.

Der Hauptgrund das so zu machen, ist daß der Konstruktor sofort aufgerufen wird wenn der Prozess startet. Dann muß ich das nicht selber machen ;)

Ja das ist schon klar. Das ist aber keine Forderung bei Singletons

Und nun sag, was stört dich daran? =)

Das ich immer noch so viele Instanzen von der Klasse erzeugen kann wie ich will bei einem Singleton darf das nicht möglich sein.

Che
2003-04-10, 19:45:06
Äääh, ich versteh das ganze jetzt überhaupt nicht ?-)

Demirug
2003-04-10, 20:10:23
Che -> http://www.object-arts.com/EducationCentre/Patterns/Singleton.htm

Che
2003-04-10, 20:18:13
Originally posted by Demirug
Che -> http://www.object-arts.com/EducationCentre/Patterns/Singleton.htm

Ich kann nicht aufhören mich über euch zu wundern. Ihr habt wohl wirklich zu jedem noch so ausgefallenen Thema Hilfe bereit!

/me glaubt: 3DC -> bestes Forum im deutsprschigen Raum (mal wieder:D)

Link hab ich erst kurz überflogen aber ich bin sicher er hilft mir weiter...:)

zeckensack
2003-04-10, 20:34:40
Originally posted by Demirug
Che -> http://www.object-arts.com/EducationCentre/Patterns/Singleton.htm Die schießen aber wirklich mit Thrombosen auf Spatzen :|
Weder Mäuse noch Bildschirme sind IMO sinnvolle Kandidaten für Singletons.
(btw, wenn das Smalltalk ist, dann bin ich froh das nicht gelernt zu haben ...)

Die simpelste Lösung für deine Forderung ist wohl, einfach alle Members static zu machen. Damit hätte ich eigentlich kein Problem. Ich hab's auch schon gemacht, aber auch wieder nur weil MSVC6 so scheiße optimiert.
Dann könnte man zwar beliebig viele Instanzen erzeugen, diese wären aber im Grunde nur Referenzen auf 'das' Objekt.
Diesen ganzen Overhead mit current etc würde ich mir nicht mal in die Haare schmieren :|

Demirug
2003-04-10, 20:42:20
Originally posted by zeckensack
Die schießen aber wirklich mit Thrombosen auf Spatzen :|
Weder Mäuse noch Bildschirme sind IMO sinnvolle Kandidaten für Singletons.
(btw, wenn das Smalltalk ist, dann bin ich froh das nicht gelernt zu haben ...)

Die simpelste Lösung für deine Forderung ist wohl, einfach alle Members static zu machen. Damit hätte ich eigentlich kein Problem. Ich hab's auch schon gemacht, aber auch wieder nur weil MSVC6 so scheiße optimiert.
Dann könnte man zwar beliebig viele Instanzen erzeugen, diese wären aber im Grunde nur Referenzen auf 'das' Objekt.
Diesen ganzen Overhead mit current etc würde ich mir nicht mal in die Haare schmieren :|

Um zu verhindern das man beliebig viele Instanzen erzeugen kann macht man einfach alle (inklusive default) Konstruktor mindestens protected und das Thema ist erledigt.

Che
2003-04-26, 19:58:33
Ok, das mit den Singletons lass ich erstmal noch...*nixdurchblick*

Aber ich hab da was neues :D
Ich habe einen Quadtree, den ich rekursiv durchlaufe. Allerdings greife ich beim abarbeiten auf andere Elemente des Baumes zu, um bestimmte Daten im aktuellen Element zu setzen. Das Datum (sing. von Daten, für die dies nicht wissen ;)) auf das ich in diesem anderen Element zugreife muss aber wiederum erst berechnet werden, und dabei muss man wieder auf andere Elemnte zugreifen... Wie wird sichergestellt, dass ich bei so einem "Querzugriff" schon die neu berechneten Daten bekomme? Oder geht das gar nicht? Dann müsste man nämlich den ganzen Baum zweimal durchlaufen: Einmal um alle Daten zu brechnen und neu zu setzen und einmal um dann mit diesen neuen Daten arbeiten zu können.
Ich hoffe das war nicht zu verworren ;)

zeckensack
2003-04-26, 20:21:08
Originally posted by Che
Ok, das mit den Singletons lass ich erstmal noch...*nixdurchblick*

Aber ich hab da was neues :D
Ich habe einen Quadtree, den ich rekursiv durchlaufe. Allerdings greife ich beim abarbeiten auf andere Elemente des Baumes zu, um bestimmte Daten im aktuellen Element zu setzen. Das Datum (sing. von Daten, für die dies nicht wissen ;)) auf das ich in diesem anderen Element zugreife muss aber wiederum erst berechnet werden, und dabei muss man wieder auf andere Elemnte zugreifen... Wie wird sichergestellt, dass ich bei so einem "Querzugriff" schon die neu berechneten Daten bekomme? Oder geht das gar nicht? Dann müsste man nämlich den ganzen Baum zweimal durchlaufen: Einmal um alle Daten zu brechnen und neu zu setzen und einmal um dann mit diesen neuen Daten arbeiten zu können.
Ich hoffe das war nicht zu verworren ;) Von wo kommen diese 'anderen Daten'?

Du kannst bei Bäumen idR so arbeiten, daß du 'abwärts' Daten sammelst, und 'aufwärts' Daten abarbeitest. Manchmal auch umgekehrt ... oder beides gleichzeitig 'abwärts' :bäh:

Wenn die Daten aus den parent nodes kommen, dann ist das IMO die leichteste Lösung.

ZB, nehmen wir mal an du hast eine hierarchische Bone-Animation, wo jeder Bone eine Transformation angibt.

Dann sammelst du auf dem Weg 'abwärts' durch die Hierarchie diese Transformationen an, indem du sie in eine gemeinsame Matrix hineinmultiplizierst, wobei du die alte Matrix auf einem Stack speicherst. Auf dem Weg zurück entfernst du dann jeweils eine Matrix vom Stack, auf die Weise kannst du dann in andere Unterbäume hineingehen, ohne von der Wurzel aus komplett neu zu rechnen.

Che
2003-04-26, 22:10:29
Tja, das ist ja eben das Problem. Ich kann nicht genau sagen woher die anderen Daten kommen, das entscheidet sich erst zur Laufzeit. Mein Quadtree benutzt keine Pointer Hierachie, ich codiere ihn direkt aus einem 2d-Array. Elemente spreche ich so an:

int new[2];
new[0] = pos[0] + len/4;
new[1] = pos[1] + len/4;

RenderNode(new);

new gibt die x/y Position im DatenArray an, in Rendernode wird dann umgerechnet:

currentPos = new[1] * len + new[0];


Da kommt es eben darauf an auf welcher Stufe ich mich befinde, d.h ob len ein großer oder ein kleiner Wert ist.

Demirug
2003-04-26, 22:22:38
Fragen:

1. Handelt es sich nur um einen oder mehrer Werte?
2. Wie aufwendig ist die Berechnung und auf wie viele Grundwerte baut sie auf?
3. Muss diese Berechung für jeden Knoten auf jeden Fall durchgeführt werden?
4. Wird der Wert nach dem berechnen nur einmal oder mehrfach gebraucht?
5. falls er mehrfach gebraucht wird: Wann steht fest wie oft er gebraucht wird?

Che
2003-04-26, 22:55:04
ad 1: ein wert
ad 2: gar nicht aufwendig / 3 grundwerte, die aber auf jeden fall schon vorliegen
ad 3: jop
ad 4: öfters
ad 5: das ist komplizierter. um das zu beantworten müsste ich die werte von anderen nodes schon kennen, und das ist ja genau das problem...

Vielleicht zur Eklärung:
Es handelt sich um ein bool, das festlegt ob das betreffenden Node weiter verfeinert wird. (->und ab in die 4 childs)
Sollte das nict der fall sein, wird das Node gerendert, und jetzt kommts: beim Rendern muss ich auf die Nachbarnodes zugreifen und das bewusste bool abfragen, das ist nötig damit alles korrekt gerendert werden kann. Wenn nun aber das Nachbarnode noch nicht "bearbeitet" wurde...

Demirug
2003-04-26, 23:00:32
Dieser bool dürfte sich doch nur dann ändern wenn du den Quadtree veränderst?

Ergo sollte man hier eigentlich nur eine neue Berechnung für die Knoten anstossen die sich geändert haben und das auch nur wenn sich etwas ändert.

zeckensack
2003-04-26, 23:08:25
Ein Terrain-Renderer mit automatischen LOD? :D

Vielleicht kannst du dir helfen, indem du die Kanten der Nodes immer passend machst, und nur den Detailgrad im inneren variierst?
(nur eine relativ schlecht durchdachte Idee, zugegeben :bäh: )

Che
2003-04-26, 23:16:37
Originally posted by
Demirug
Dieser bool dürfte sich doch nur dann ändern wenn du den Quadtree veränderst?

Ergo sollte man hier eigentlich nur eine neue Berechnung für die Knoten anstossen die sich geändert haben und das auch nur wenn sich etwas ändert.


und

Originally posted by zeckensack
Ein Terrain-Renderer mit automatischen LOD? :D

Vielleicht kannst du dir helfen, indem du die Kanten der Nodes immer passend machst, und nur den Detailgrad im inneren variierst?
(nur eine relativ schlecht durchdachte Idee, zugegeben :bäh: )

@ zecke: Du hasts erfasst :)
Die Idee hatte ich auchschon: Verschiedene "Kantensets" erstellen (gibt ja nur drei Möglichkeiten: angrenzendes Node ist gleich, eine Detailstufe höher, oder eine tiefer) und dann je nachdem diese benutzen. Dann könnte man das "Innere" auch mit Strips realisieren und ich bräuchte nicht mit 10-Vertice-Fans Leistung verschwenden.

@ Demirug:
Ja, aber da das ganze von der Kameraposition abhängt ändert sich praktisch die ganze Zeit der ganze Baum :bäh:

Demirug
2003-04-26, 23:57:52
Che, optimiert für Hardware oder Software T&L?

Sobald man Hardware T&L verwendet passiert es leider immer mal wieder gernen das ein Verfahren bei dem man eigentlich eine ganze Menge Verticen einspart am ende nichts bringt weil man sich eine CPU limitierung eingehandelt hat.

Che
2003-04-27, 00:10:52
Mit 550 Millionen Hertzen und einer non T&L Karte lautet die oberste Prämisse: Vertices sparen. und zwar mit allen Mitteln :naughty:

Der Problematik einer CPU Limitierung bei HW T&L bin ich mir bewusst. Aber da reicht es schon die minimale Nodelänge auf 16 anzuheben (z.Z. rattere ich hinunter bis 3 - kleiner geht nimmer) und schwups - ich habe 256 Vertices anstat 9 pro Node.