2010-04-05 10 views
22

Ich habe ein C++ - Programm, das eine std :: list verwendet, die Instanzen einer Klasse enthält. Wenn ich z.B. myList.push_back(MyClass(variable)); es durchläuft den Prozess der Erstellung einer temporären Variable, und kopiert dann sofort in den Vektor und löscht anschließend die temporäre Variable. Dies ist nicht annähernd so effizient wie ich es will, und saugt, wenn Sie eine tiefe Kopie benötigen.So initialisieren Sie einen AWL-Vektor/eine Liste mit einer Klasse, ohne den Kopierkonstruktor aufzurufen

Ich hätte gerne den Konstruktor meiner Klasse new etwas und muss nicht einen Kopierkonstruktor implementieren, nur um meinen Speicher für das zweite Mal zuzuordnen und verschwenden Laufzeit. Ich möchte auch nicht sofort die Klasseninstanz aus dem Vektor/der Liste finden und dann manuell den Speicher zuweisen (oder etwas Schreckliches tun, wie den Speicher im Kopierkonstruktor selbst zuweisen).

Gibt es einen Weg um dies (ich verwende nicht Visual Studio BTW)?

+1

Welchen Compiler benutzen Sie? Ich glaube, dass dieser Fall normalerweise in neueren Compilern optimiert wird. –

+0

Führen Sie diese Analyse mit einem optimierten Build durch? –

+0

Sie müssen mit einem Release-Build testen. Es kann dies im DEBUG-Modus tun, aber verwenden Sie RVO (Rückgabewert-Optimierung) im Freigabemodus, die die Kopie beseitigt. – Nate

Antwort

9

C++ 0x-Verschiebungskonstruktoren sind eine teilweise Problemumgehung: anstelle des Kopierkonstruktors, der aufgerufen wird, wäre der Verschiebungskonstruktor. Der Verschiebungskonstruktor ähnelt dem Kopierkonstruktor, außer dass das Quellenargument ungültig gemacht werden darf.

C++ 0x fügt ein weiteres Feature, das genau das tun würde, was Sie wollen: emplace_back. (N3092 §23.2.3) übergeben Sie es die Argumente an den Konstruktor, dann ruft es den Konstruktor mit diesen Argumenten (von ... und Forwarding), damit kein anderer Konstruktor jemals aufgerufen werden kann.

Wie bei C++ 03 besteht Ihre einzige Option darin, Ihrer Klasse einen nicht initialisierten Status hinzuzufügen. Führen Sie die eigentliche Konstruktion in einer anderen Funktion aus, die unmittelbar nach push_back aufgerufen wird. boost::optional kann Ihnen dabei helfen, die Initialisierung von Klassenmitgliedern zu vermeiden, erfordert aber wiederum Sie müssen kopierfähig sein. Oder, wie Fred sagt, das Gleiche mit anfänglich leeren Smart Pointern erreichen.

+0

Das ist mehr, was ich suche. Ich habe einen C++ 0x-fähigen Compiler und wie die Bequemlichkeit von std :: lists ohne den Speicher/Heap-Allokationsnachteil einer großen Anzahl von Zeigern. Komisch, ich habe noch nie von emplace_back gehört, was ich bekomme, wenn ich nur auf den Wikipedia-Eintrag für C++ 0x schaue. Wo finde ich weitere Informationen? – Warpspace

+0

@Warpspace: Laden Sie N3092 herunter (googeln Sie nach "C++ N3092") und sehen Sie sich die C++ 0x FAQ von Stroustrup an http://www2.research.att.com/~bs/C++0xFAQ.html. Ich habe bemerkt, dass Microsoft eine falsche Dokumentation für "emplace" hat und behauptet, dass es die move-Semantik verwendet. (Dasselbe wird mit 'push_back (move (...)) 'erreicht.) Wenn Sie also auf MSVC sind, haben Sie im Moment vielleicht noch kein Glück. – Potatoswatter

4

Tatsächlich könnte der Compiler in diesem Fall die Kopie entfernen.

Wenn Ihr Compiler das nicht tut, wäre eine Möglichkeit, das Kopieren zu vermeiden, darin zu sehen, dass Ihre Liste Zeiger statt Instanzen enthält. Sie könnten intelligente Zeiger verwenden, um die Objekte für Sie zu bereinigen.

+2

Der Compiler kann die Kopie nicht freigeben. Das Objekt wird auf dem Stapel konstruiert und durch const-Verweis auf "vector <> :: allocator_type :: construct" übergeben, um es auf den Heap zu verschieben. Es wird aufgebaut, bevor der Aufruf von 'push_back' beginnt und der Zielort nicht bekannt ist, bis' allocator_type :: allocate' in 'push_back' steht. Die Kräfte von Zeit und Raum kooperieren nicht. – Potatoswatter

2

Überprüfen Sie Boost ptr_container Bibliothek. Ich verwende den ptr_vector insbesondere:

boost::ptr_vector<Foo> c; 
c.push_back(new Foo(1,2,3)); 
c[0].doSomething() 

und wenn es den Bereich verlässt, delete wird an jedem Element des Vektors bezeichnet werden.

+1

... ohne den Overhead von 'shared_ptr' - +1 – vladr

1

Verwenden Sie shared_ptr oder shared_array, um den Speicher zu verwalten, den Ihre Klasse zuweisen möchte. Dann wird der vom Compiler bereitgestellte Kopierkonstruktor einfach eine Referenzzählung inkrementieren, wenn sich die shared_ptr selbst kopiert. Es ist ein wichtiges Nutzungskonzept für Standardcontainer, dass Ihre Elemente kostengünstig zu kopieren sind. Die Standardbibliothek erstellt überall Kopien.

5

C++ 0x Move-Konstruktoren (verfügbar mit VC++ 2010 und aktuelle GNU-Compiler) sind genau das, wonach Sie suchen.

+0

Verschiebungskonstruktoren erfordern einen nicht initialisierten Zustand für das Objekt. Wenn er einen nicht initialisierten Zustand hat, kann er dies standardmäßig mit 'push_back (MyClass())' konstruieren und dann sein Objekt an seinem Platz 'initialisieren'. Also glaube ich nicht, dass 'move' hier etwas löst. – Potatoswatter

+0

Ich bin nicht sicher, ob es mein Compiler ist oder nicht, aber mit C++ 0x (unter G ++) aktiviert, scheint der Move-Konstruktor nicht aufgerufen zu werden, da mein Objekt zweimal gelöscht wird (was bedeutet, dass der Kopierkonstruktor verwendet wird). Was Potatocorn erwähnt, ist meine aktuelle Implementierung, die ich aufgrund der strengen Code-Duplizierung (oder der Notwendigkeit einer dedizierten Funktion) vermeiden möchte. – Warpspace

9

Ähem. Im Interesse der Wissenschaft, ich habe ein kleines Testprogramm aufgepeitscht zu prüfen, ob der Compiler der Kopie oder nicht elides:

#include <iostream> 
#include <list> 
using namespace std; 

class Test 
{ 
public: 
    Test() { cout<<"Construct\n"; } 
    Test(const Test& other) { cout<<"Copy\n"; } 
    Test& operator=(const Test& other) { cout<<"Assign\n"; return (*this); } 
}; 

Test rvo() { return Test(); } 
int main() 
{ 
    cout<<"Testing rvo:\n"; 
    Test t = rvo(); 
    cout<<"Testing list insert:\n"; 
    list<Test> l; 
    l.push_back(Test()); 
} 

Und hier ist meine Ausgabe auf MSVC++ 2008:

 
Testing rvo: 
Construct 
Testing list insert: 
Construct 
Copy 

Es ist das gleiche für Debug und Release-Builds: RVO funktioniert, temporäre Objektübergabe ist nicht optimiert.
Wenn ich mich nicht irre, ist die Rvalue references, die im C++ 0x-Standard hinzugefügt wird, genau dieses Problem zu lösen.

+1

+1 für ** Wissenschaft **! –

0

Ich würde vorschlagen, eine std::vector<std::unique_ptr>, weil es automatisch den Speicher bei Bedarf freigibt, mit weniger Aufwand als std::shared_ptr. Der einzige Nachteil ist, dass dieser Zeiger keine Referenzzählung hat, und Sie müssen also darauf achten, den Zeiger selbst nicht woanders zu kopieren, damit die Daten nicht gelöscht werden, wenn sie noch woanders verwendet werden.

Verwandte Themen