2013-07-21 3 views
5

Lesen über std::unique_ptr bei http://en.cppreference.com/w/cpp/memory/unique_ptr, mein naive Eindruck ist, dass ein klug genug Compiler die korrekte Verwendung von unique_ptr mit blanken Zeigern ersetzen könnte und einfach in eine delete setzen, wenn die unique_ptr s zerstört werden. Ist das tatsächlich der Fall? Wenn ja, tut dies einer der Mainstream-optimierenden Compiler? Wenn nicht, wäre es möglich, etwas mit einigen/allen unique_ptr s Kompilierungszeit-Sicherheitsvorteilen zu schreiben, die optimiert werden könnten, um keine Laufzeitkosten (in Raum oder Zeit) zu haben?Könnte ein optimierender Compiler alle Laufzeitkosten von std :: unique_ptr entfernen?

Hinweis für diejenigen (richtig) besorgt über vorzeitige Optimierung: Die Antwort hier wird mich nicht davon abhalten, std::unique_ptr zu verwenden, bin ich nur neugierig, ob es ein wirklich tolles Werkzeug oder einfach nur ein tolles ist.

EDIT 2013.07.21 20.07 EST:

OK, so dass ich mit folgendem Programm getestet (wissen Sie mich bitte, wenn mit diesem etwas falsch gibt es):

#include <climits> 
#include <chrono> 
#include <memory> 
#include <iostream> 

static const size_t iterations = 100; 

int main (int argc, char ** argv) { 
    std::chrono::steady_clock::rep smart[iterations]; 
    std::chrono::steady_clock::rep dumb[iterations]; 
    volatile int contents; 
    for (size_t i = 0; i < iterations; i++) { 
     auto start = std::chrono::steady_clock::now(); 
     { 
      std::unique_ptr<int> smart_ptr(new int(5)); 
      for (unsigned int j = 0; j < UINT_MAX; j++) 
       contents = *smart_ptr; 
     } 
     auto middle = std::chrono::steady_clock::now(); 
     { 
      int *dumb_ptr = new int(10); 
      try { 
       for (unsigned int j = 0; j < UINT_MAX; j++) 
        contents = *dumb_ptr; 
       delete dumb_ptr; 
      } catch (...) { 
       delete dumb_ptr; 
       throw; 
      } 
     } 
     auto end = std::chrono::steady_clock::now(); 
     smart[i] = (middle - start).count(); 
     dumb[i] = (end - middle).count(); 
    } 
    std::chrono::steady_clock::rep smartAvg; 
    std::chrono::steady_clock::rep dumbAvg; 
    for (size_t i = 0; i < iterations; i++) { 
     smartAvg += smart[i]; 
     dumbAvg += dumb[i]; 
    } 
    smartAvg /= iterations; 
    dumbAvg /= iterations; 

    std::cerr << "Smart: " << smartAvg << " Dumb: " << dumbAvg << std::endl; 
    return contents; 
} 

Das Kompilieren mit g ++ 4.7.3 unter Verwendung von g++ --std=c++11 -O3 test.cc ergab Smart: 1130859 Dumb: 1130005, was bedeutet, dass der intelligente Zeiger innerhalb von 0,076% des dummen Zeigers liegt, was fast sicher Rauschen ist.

+4

Was * sonst * würde ein Compiler möglicherweise tun ?! Ein eindeutiger Zeiger * ist * nur ein einzelner Zeiger, soweit der Dateninhalt der Klasse betroffen ist. –

+0

Welche Laufzeitkosten hat ein 'unique_ptr 'überhaupt? Was Platz angeht, so sind 'sizeof (myuniqueptr)' vs 'sizeof (myptr)' genau die gleichen für mich, 8 Bytes für 'int'. – Rapptz

+0

Nun, das Lesen von http://StackOverflow.com/Questions/8138284/about-unique-PTR-Performances deutet darauf hin, dass einige (frühe?) Implementierungen nicht so waren ... –

Antwort

5

Es wäre sicherlich meine Erwartung von jedem einigermaßen kompetenten Compiler sein, da es nur ein Wrapper um einen einfachen Zeiger und ein destructor ist die delete nennt, so dass der Machne Code vom Compiler generierte für:

x *p = new X; 
... do stuff with p. 
delete p; 

und

unique_ptr<X> p(new X); 
... do stuff with p; 

wird genau der gleiche Code sein.

+1

Mehr wie 'x * p; versuche {p = neues X;/* ... * /} fang (...) {lösche p; werfen; } '... –

+0

Muss' unique_ptr' wirklich versuchen/fangen? - In beiden Fällen wird ein Fehler bei der Zuweisung in "neu" die ganze Funktion einfach wegwerfen, entweder mit dem einfachen Zeiger "p" oder dem intelligenten Zeiger "p" nicht "konstruiert" (in einem lockeren Sinn von konstruiert in der frühere Fall). Ich denke, wenn der Konstruktor oder 'X' wirft, müssen Sie sich darum kümmern ... –

+2

Es geht um Ausnahmen im Rest Ihres Codes! (Also hätte ich 'x * p = neues X sagen sollen; versuchen {/ * ... * /} fangen (...) {delete p; werfen;}'.) –

4

Streng genommen ist die Antwort nein.

Erinnern Sie sich, dass unique_ptr eine Vorlage parametrisiert ist nicht nur auf den Typ des Zeigers, sondern auch auf den Typ der Löscher. Seine Erklärung ist:

template <class T, class D = default_delete<T>> class unique_ptr; 

Zusätzlich unique_ptr<T, D> enthält nicht nur eine T*, sondern auch eine D. Der folgende Code (die auf MSVC 2010 und GCC kompiliert 4.8.1) stellt den Punkt:

#include <memory> 

template <typename T> 
struct deleter { 
    char filler; 
    void operator()(T* ptr) {} 
}; 

int main() { 
    static_assert(sizeof(int*) != sizeof(std::unique_ptr<int, deleter<int>>), ""); 
    return 0; 
} 

Wenn Sie eine unique_ptr<T, D> die Kosten bewegen sich nicht nur, dass von den T* von der Quelle kopieren Ziel (wie es wäre, mit einem rohen Zeiger), da es auch eine kopieren/verschieben muss.

Es ist wahr, dass intelligente Implementierungen erkennen kann, wenn D leer ist und ein Kopieren/Verschieben Konstruktor, der nichts tut (dies ist der Fall von default_delete<T>), und in einem solchen Fall vermeiden den Overhead eines D kopieren. Darüber hinaus kann Speicher gespart werden, indem kein zusätzliches Byte für hinzugefügt wird.

unique_ptr Der Destruktor muss prüfen, ob T* Null ist oder nicht, bevor der Deleter aufgerufen wird.Für defalt_delete<T> Ich glaube, der Optimierer könnte diesen Test beseitigen, da es in Ordnung ist, einen Nullzeiger zu löschen.

Allerdings gibt es eine zusätzliche Sache, die std::unique_ptr<T, D> 's Move Constructor muss tun und T* ist nicht. Da der Besitz von Quelle zu Ziel weitergegeben wird, muss die Quelle auf Null gesetzt werden. Ähnliche Argumente gelten für Zuweisungen von unique_ptr.

Mit dem gesagt, für die default_delete<T>, ist der Overhead so klein, dass ich glaube, wird sehr schwer durch Messungen zu erkennen sein.

+0

Ich denke, es ist optimal genug, solange die Größe (default_delete) und * -Operator Null Overhead sind, unique_ptr ist fast ein perfektes Werkzeug, um den Speicher durch rohe Zeiger zu schützen. – StereoMatching

Verwandte Themen