Hallo Welt
Ich habe mich an einer kleinen Scriptengine versucht und bin soweit auch recht erfolgreich. Zwar sollte die dazugehörige Sprache soweit typesafe sein, dass es Typen gibt, diese aber bei Bedarf unter Ausgabe einer Warnung implizit umgewandelt werden können. Also zum Beispiel
Dazu wollte ich eine Klasse nutzen, die alle Typen darstellen kann. Dazu habe ich eine Klasse erstellt, die einen Member "int type" hat, der die verschiedenen Typen darstellt (string, int, float, etc.).
Die Daten (bzw. der Wert des Typs) werden in "void* data" gespeichert und je nach Typ wird anders gecastet und operiert.
Das Grundgerüst sieht also so aus:
Und ja, ich habe den Speicher schon im Griff, auch mit Copy-Operations u.ä.
Was dem Leser jetzt wahrscheinlich schon durch den Kopf geht, ist: "Warum nicht als Basisklasse und Kindklassen realisieren?".
Diese Idee kam mir, nachdem ich schon einen Grossteil geschrieben hatte (ja, C lässt mich einfach nicht los
). Doch was ich mich fragte, war, ob es denn effizienter ist, mit Vererbung zu arbeiten. Dazu folgendes: Meine jetzige dynVar-Klasse umfasst 20 "normale" (also nicht-virtuelle) Funktionen sowie folgende Membervariabeln:
Also 12 Bytes (32bit) bis 20 Bytes (64bit). Der Compiler bestätigt mir mit sizeof() die erwarteten 12 Bytes.
Nun habe ich ein bisschen mit dem Compiler gespielt:
Die Definitionen zu den Klassen:
Bevor wir uns die Ausgabe anschauen: ASM
Und schliesslich die Ausgabe:
Das 1 kommt wohl nur daher, dass sizeof() nicht gerne 0 hat. Alles andere sagt hingegen folgendes aus:
* alle Klassen, die mindestens 1 "virtual"-Keyword haben, brauchen mindestens 4 Bytes.
* alle Klassen, die mindestens eine virtuelle Funktion überschreiben, brauchen mindestens 4 Bytes.
* alle Klassen, die mindestens eine virtuelle Funktion haben, erfordern einen (Default-) Konstruktoraufruf.
* alle Klassen, 1) die keine virtuelle Funktion beinhalten und
2) für die keine der ersten drei Punkte zutreffen und keine Klasse beinhalten erfordern keinen Konstruktoraufruf.
Fügt man bei class Test1 noch ein "int i;" ein, dann sind Test1 und Test3 jeweils 8 Bytes gross. Daher:
*Die oben genannten Grössen werden zu der Grösse der Variabeln addiert.
Das Verhalten konnte unter Visual Studio 2013 (v120) sowohl im Debug- als auch im Releasemode auf 32bit-Architektur beobachtet werden. Ohne es überprüft zu haben, rechne ich mit 8 statt 4 Bytes auf 64bit.
Auf meinen Anwendungsfall bezogen heisst das folgendes:
Eine Basisklasse mit virtuellen Funktionen und dem Reference Counter (Für Memory-Management): 4 + 4 = 8 Bytes (immer vorhanden).
Für Strings:
(Da ein std::string 28 Bytes gross ist, wird mit void* bzw. char* gearbeitet.)
* char* str: 4 Bytes
Total: 12 Bytes
Für Ints:
* int i: 4 Bytes
Total: 12 Bytes
(auf 64bit jeweils 8 + 8 + 8 bzw. 8 + 8 + 4 Bytes)(ungetestet)
Zudem:
* Der Konstruktor, wenn auch compilergeneriert, wird ausgeführt und weist die virtuellen Funktionen zu.
Fazit:
Man braucht eher noch mehr Ressourcen mit dem komplett-C++-Ansatz als mit dem C++-C-Mischmasch.
Nun zu meinen Fragen:
Das wichtigste zuerst: Wo habe ich, falls dem so ist, einen Fehler gemacht?
Falls keine Antwort auf die erste Frage (=wenn ich alles richtig interpretiert habe): Gibt es einen Grund, warum man (in diesem Beispiel) dennoch Vererbung benutzen sollte?
Gibt es einen Grund, warum man meinen Erstansatz NICHT verwenden sollte?
Konzeptbetreffend:
Vererbung scheint bei kleinen Klassen vergleichsweise ineffizient. Warum wird es dennoch so oft benutzt?
Ich bin mir sicher, ich hatte noch einige Fragen mehr. Diese fallen mir jetzt aber leider nicht mehr ein
Gruss
cwriter
Ich habe mich an einer kleinen Scriptengine versucht und bin soweit auch recht erfolgreich. Zwar sollte die dazugehörige Sprache soweit typesafe sein, dass es Typen gibt, diese aber bei Bedarf unter Ausgabe einer Warnung implizit umgewandelt werden können. Also zum Beispiel
Code:
string zahl = "5";
zahl = 3;
//zahl wird zu int, eine Warnung wird ausgegeben
//Um das klarzustellen: Natürlich werde ich daran denken, fromInt()-Ähnliche Funktionen bereitzustellen
Dazu wollte ich eine Klasse nutzen, die alle Typen darstellen kann. Dazu habe ich eine Klasse erstellt, die einen Member "int type" hat, der die verschiedenen Typen darstellt (string, int, float, etc.).
Die Daten (bzw. der Wert des Typs) werden in "void* data" gespeichert und je nach Typ wird anders gecastet und operiert.
Das Grundgerüst sieht also so aus:
C++:
class dynVar
{
int type;
void* data;
};
Was dem Leser jetzt wahrscheinlich schon durch den Kopf geht, ist: "Warum nicht als Basisklasse und Kindklassen realisieren?".
Diese Idee kam mir, nachdem ich schon einen Grossteil geschrieben hatte (ja, C lässt mich einfach nicht los

C++:
int type; //4 Bytes
void* data; //4 Bytes auf 32bit, 8 Bytes auf 64bit
size_t* m_reference_count; //4 Bytes auf 32bit, 8 Bytes auf 64bit
Nun habe ich ein bisschen mit dem Compiler gespielt:
C++:
Test1 _t1;
_t1.test();
Test2 _t2;
_t2.test();
Test3 _t3;
_t3.test();
Test4 _t4;
_t4.test();
printf("Test1: %d\nTest2: %d\nTest3: %d\nTest4: %d\nTest5: %d\n\n", (int)sizeof(Test1), (int)sizeof(Test2), (int)sizeof(Test3), (int)sizeof(Test4), (int)sizeof(Test5));
Die Definitionen zu den Klassen:
C++:
class Test1 {
public:
virtual void test() { printf("Test1"); }
};
class Test2 {
public:
void test() { printf("Test2"); }
};
class Test3 : publicTest1{
public:
void test() override { printf("Test3"); }
};
class Test4 {
public:
virtual void test() { printf("Test4"); }
virtual void test_2() { printf("Test4_2"); }
};
class Test5 {
public:
virtual void test() = 0;
virtual void test_2() = 0;
};
Bevor wir uns die Ausgabe anschauen: ASM

Code:
Test1 _t1;
0032A955 lea ecx,[_t1]
0032A958 call Test1::Test1 (029DBA1h)
_t1.test();
0032A95D lea ecx,[_t1]
0032A960 call Test1::test (029DB9Ch)
Test2 _t2;
_t2.test();
0032A965 lea ecx,[_t2]
0032A968 call Test2::test (029DBA6h)
Test3 _t3;
0032A96D lea ecx,[_t3]
0032A970 call Test3::Test3 (029DBB0h)
_t3.test();
0032A975 lea ecx,[_t3]
0032A978 call Test3::test (029DBABh)
Test4 _t4;
0032A97D lea ecx,[_t4]
0032A980 call Test4::Test4 (029DBB5h)
_t4.test();
0032A985 lea ecx,[_t4]
0032A988 call Test4::test (029DBBFh)
printf("Test1: %d\nTest2: %d\nTest3: %d\nTest4: %d\nTest5: %d\n\n", (int)sizeof(Test1), (int)sizeof(Test2), (int)sizeof(Test3), (int)sizeof(Test4), (int)sizeof(Test5));
Und schliesslich die Ausgabe:
Code:
Test1: 4
Test2: 1
Test3: 4
Test4: 4
Test5: 4
Das 1 kommt wohl nur daher, dass sizeof() nicht gerne 0 hat. Alles andere sagt hingegen folgendes aus:
* alle Klassen, die mindestens 1 "virtual"-Keyword haben, brauchen mindestens 4 Bytes.
* alle Klassen, die mindestens eine virtuelle Funktion überschreiben, brauchen mindestens 4 Bytes.
* alle Klassen, die mindestens eine virtuelle Funktion haben, erfordern einen (Default-) Konstruktoraufruf.
* alle Klassen, 1) die keine virtuelle Funktion beinhalten und
2) für die keine der ersten drei Punkte zutreffen und keine Klasse beinhalten erfordern keinen Konstruktoraufruf.
Fügt man bei class Test1 noch ein "int i;" ein, dann sind Test1 und Test3 jeweils 8 Bytes gross. Daher:
*Die oben genannten Grössen werden zu der Grösse der Variabeln addiert.
Das Verhalten konnte unter Visual Studio 2013 (v120) sowohl im Debug- als auch im Releasemode auf 32bit-Architektur beobachtet werden. Ohne es überprüft zu haben, rechne ich mit 8 statt 4 Bytes auf 64bit.
Auf meinen Anwendungsfall bezogen heisst das folgendes:
Eine Basisklasse mit virtuellen Funktionen und dem Reference Counter (Für Memory-Management): 4 + 4 = 8 Bytes (immer vorhanden).
Für Strings:
(Da ein std::string 28 Bytes gross ist, wird mit void* bzw. char* gearbeitet.)
* char* str: 4 Bytes
Total: 12 Bytes
Für Ints:
* int i: 4 Bytes
Total: 12 Bytes
(auf 64bit jeweils 8 + 8 + 8 bzw. 8 + 8 + 4 Bytes)(ungetestet)
Zudem:
* Der Konstruktor, wenn auch compilergeneriert, wird ausgeführt und weist die virtuellen Funktionen zu.
Fazit:
Man braucht eher noch mehr Ressourcen mit dem komplett-C++-Ansatz als mit dem C++-C-Mischmasch.
Nun zu meinen Fragen:
Das wichtigste zuerst: Wo habe ich, falls dem so ist, einen Fehler gemacht?
Falls keine Antwort auf die erste Frage (=wenn ich alles richtig interpretiert habe): Gibt es einen Grund, warum man (in diesem Beispiel) dennoch Vererbung benutzen sollte?
Gibt es einen Grund, warum man meinen Erstansatz NICHT verwenden sollte?
Konzeptbetreffend:
Vererbung scheint bei kleinen Klassen vergleichsweise ineffizient. Warum wird es dennoch so oft benutzt?
Ich bin mir sicher, ich hatte noch einige Fragen mehr. Diese fallen mir jetzt aber leider nicht mehr ein

Gruss
cwriter
Zuletzt bearbeitet: