2010-01-06 8 views
9

Angenommen, ich eine Klasse wieWird beim Aufruf des Konstruktors einer leeren Klasse tatsächlich Speicher verwendet?

class Empty{ 
    Empty(int a){ cout << a; } 
} 

haben Und dann rufe ich es mit

int main(){ 
    Empty(2); 
    return 0; 
} 

Wird diese Ursache jeder Speicher auf dem Stapel für die Schaffung eines „leeren“ Objekt zugewiesen werden? Offensichtlich müssen die Argumente auf den Stapel geschoben werden, aber ich möchte keinen zusätzlichen Aufwand verursachen. Im Grunde benutze ich den Konstruktor als statisches Element.

Der Grund, warum ich dies tun möchte, ist wegen der Vorlagen. Der eigentliche Code sieht aus wie

template <int which> 
class FuncName{ 
    template <class T> 
    FuncName(const T &value){ 
     if(which == 1){ 
      // specific behavior 
     }else if(which == 2){ 
      // other specific behavior 
     } 
    } 
}; 

, die mich so etwas wie

int main(){ 
    int a = 1; 
    FuncName<1>(a); 
} 

so schreiben können, dass ich ein Template-Parameter spezialisieren erhalten, obwohl sie nicht die Art von T angeben zu müssen. Ich hoffe auch, dass der Compiler die anderen Zweige innerhalb des Konstruktors optimiert. Wenn jemand weiß, ob das wahr ist oder wie man es überprüft, würde das sehr geschätzt werden. Ich nahm auch an, dass das Werfen von Schablonen in die Situation das "leere Klassen" -Problem von oben nicht ändert, ist das richtig?

+0

Die Frage ist, warum kümmert es dich.Es ist die Aufgabe des Compilers, den besten Code zu pflegen und zu generieren. Sie sollten sich darauf konzentrieren, den aussagekräftigsten Code zu schreiben. –

+0

PS. Das Argument muss nicht auf den Stack geschoben werden. Die C++ - ABI ist delibatly nicht definiert, so dass Compiler die Möglichkeit haben, Register zu verwenden, um Parameter zu übergeben, wenn das den Code effizienter macht –

+0

Ich interessiere mich, weil ich die höchste Leistung will, die ich bekommen kann; Ich hasse wirklich die Einstellung, dass Code elegant sein sollte und sich nicht um diese Dinge kümmern sollte. Manchmal sind diese Dinge wichtig (ich mache High Performance Computing). –

Antwort

16

Zitiert Stroustrup:

Warum ist die Größe einer leeren Klasse nicht Null? Um sicherzustellen, dass die Adressen von zwei verschiedenen Objekten unterschiedlich sind. Aus demselben Grund gibt "neu" immer Zeiger auf unterschiedliche Objekte zurück. Bedenken Sie:

class Empty { }; 

void f() 
{ 
    Empty a, b; 
    if (&a == &b) cout << "impossible: report error to compiler supplier"; 

    Empty* p1 = new Empty; 
    Empty* p2 = new Empty; 
    if (p1 == p2) cout << "impossible: report error to compiler supplier"; 
} 

Es gibt eine interessante Regel, die besagt, dass eine leere Basisklasse muss nicht durch ein separates Byte dargestellt werden:

struct X : Empty { 
    int a; 
    // ... 
}; 

void f(X* p) 
{ 
    void* p1 = p; 
    void* p2 = &p->a; 
    if (p1 == p2) cout << "nice: good optimizer"; 
} 

Diese Optimierung ist sicher und kann sehr nützlich. Es ermöglicht einem Programmierer, leere Klassen zu verwenden, um sehr einfache Konzepte ohne Overhead darzustellen. Einige aktuelle Compiler bieten diese "leere Basisklassenoptimierung".

+0

Aber was, wenn das erstellte Objekt überhaupt nicht referenziert wird? Ist es dem Compiler erlaubt, entweder keinen Stack-Platz dafür zu reservieren, oder ihn sofort zu säubern, obwohl er nicht außerhalb des Bereichs liegt? –

+0

Ja, ich bin mir sicher, dass ein Compiler den reservierten Speicher komplett eliminiert, wenn er nicht verwendet oder referenziert wird. Es ist eine ziemlich einfache Optimierung. Abhängig vom Compiler natürlich. –

+1

Solange das Behavior nicht geändert wird, kann der Compiler das gesamte Konzept des Objekts unter der Haube eliminieren und das Ganze kann in den Code und alle Werte, die in Registern gespeichert sind, eingezeichnet werden. –

2

Es könnte, könnte es, nicht, je nach Umständen. Wenn Sie sagen:

Empty e; 
Empty * ep = & e; 

dann müssen natürlich Dinge zugewiesen werden.

+0

Muss der Compiler unbedingt den Code _allocate_ etwas ausgeben, um ihm eine Adresse zu geben? Gibt es einen Grund, warum & e keine Adresse sein kann, die weder Heap noch Stack ist und wo es kein tatsächliches Objekt gibt? – James

+0

@Autopulated: Ich glaube schon, siehe Richard Pennington's Kommentar. Die interessantere Frage ist, wenn Sie nie nach seiner Adresse fragen; Muss es dann noch einen haben? (Eine sehr zenartige Frage) –

+1

Wenn Sie die Adresse von e nehmen und dann etwas mit dieser Adresse machen (was mein Beispielcode nicht tut), dann muss e instanziiert werden - d. H. Zugewiesen werden. –

2

Probieren Sie es aus und sehen. Viele Compiler werden solche temporären Objekte eliminieren, wenn sie aufgefordert werden, ihre Ausgabe zu optimieren.

Wenn die Demontage zu komplex ist, erstellen Sie dann zwei Funktionen mit einer unterschiedlichen Anzahl solcher Objekte und sehen, ob es einen Unterschied in den Stapelstellen von Objekten ist sie umgeben, so etwas wie:

void empty1 (int x) 
{ 
    using namespace std; 

    int a; 
    Empty e1 (x); 
    int b; 

    cout << endl; 
    cout << "empty1" << endl; 
    cout << hex << int (&x) << " " << dec << (&x - &a) << endl; 
    cout << hex << int (&a) << " " << dec << (&a - &b) << endl; 
} 

und dann versuchen, läuft das im Vergleich zu einer empty8 Funktion mit acht Leerstellen erstellt. Wenn Sie bei g ++ auf x86 die Adresse eines Leerguts verwenden, erhalten Sie eine Position zwischen x und a auf dem Stapel, also einschließlich x in der Ausgabe. Sie können nicht davon ausgehen, dass der Speicher für Objekte in der Reihenfolge enden wird, in der sie im Quellcode deklariert sind.

+0

Wie? Ich schaue mir die g ++ Assembly-Liste für ein einfaches Beispiel an und es ist sehr schwer zu folgen. Ich benutze 'g ++ -c -g-O2 -Wa, -ahl = file.s file.cpp' –

+0

Sie könnten besser kompilieren zu einer ausführbaren Datei (mit Debug-Informationen), dann verwenden Sie' objdump-dS', und Suchen Sie nach den Quellzeilen, an denen Sie interessiert sind. Da Sie die Optimierung aktiviert haben, denken Sie daran, dass dieselbe Quelllinie an mehreren Stellen in der Disassemblierung erscheinen kann. –