2016-05-17 7 views
15

Also, wenn shared_ptr<Type> verwenden Sie schreiben:Warum Shared_ptr keine direkte Zuordnung ermöglichen

shared_ptr<Type> var(new Type()); 

Ich frage mich, warum sie nicht eine viel einfachere und besser (imo) zuließ:

shared_ptr<Type> var = new Type(); 

Statt eine solche Funktionalität zu erreichen, müssen Sie .reset() verwenden:

shared_ptr<Type> var; 
var.reset(new Type()); 

ich bin es gewohnt, OpenCV Ptr-Klasse, die ein Smart-Pointer ist die direkte Zuordnung und alles

+3

Da 'std :: shared_ptr's Konstruktor, der Zeiger nimmt, ist' explizit' und es gibt kein 'operator =' das Nehmen des Zeigers. – Jarod42

+9

Es ist keine Aufgabe. – LogicStuff

Antwort

20

Das Problem mit so dass ein Rohzeiger implizit in eine std::shared_ptr umgewandelt werden können mit

void foo(std::shared_ptr<int> bar) { /*do something, doesn't matter what*/ } 

int main() 
{ 
    int * bar = new int(10); 
    foo(bar); 
    std::cout << *bar; 
} 

Wenn nun die nachgewiesen werden implizite Umwandlung arbeitete der Speicher bar Punkte zu würde durch die shared_ptr Destruktor am Ende dergelöscht werden. Wenn wir auf std::cout << *bar; zugreifen, haben wir jetzt undefiniertes Verhalten, da wir einen gelöschten Zeiger dereferenzieren.

In Ihrem Fall erstellen Sie den Zeiger direkt an der Aufruf-Website, so ist es egal, aber wie Sie aus dem Beispiel sehen können, kann es Probleme verursachen.

8

Warum [nicht] shared_ptr erlaubt die direkte Zuordnung [Kopie Initialisierung] funktioniert gut erlaubt?

Weil es explicit ist, siehe here und here.

Ich frage mich, was die Begründung [ist] dahinter? (Aus einem Kommentar nun entfernt)

TL; DR, jede Konstruktor machen (oder gegossen) explicit ist es zu verhindern, dass in impliziten Konvertierungssequenzen beteiligt.

Die Anforderung für die explicit ist besser illustriert mit der shared_ptr<> ist ein Argument für eine Funktion.

void func(std::shared_ptr<Type> arg) 
{ 
    //... 
} 

Und so genannt;

Type a; 
func(&a); 

Dies würde kompilieren, und wie geschrieben und ist unerwünscht und falsch; es wird sich nicht wie erwartet verhalten.

Es wird noch komplizierter mit dem Hinzufügen von benutzerdefinierten (impliziten) Konvertierungen (Casting-Operatoren) in den Mix.

struct Type { 
}; 

struct Type2 { 
    operator Type*() const { return nullptr; } 
}; 

Dann wird die folgende Funktion (wenn nicht explizit) würde kompilieren, bietet aber einen schrecklichen Fehler ...

Type2 a; 
func(a); 
16

erlauben diese direkt Funktionen mit Zeigerargumente aufrufen können, die fehleranfällig ist weil Sie auf der Aufrufsite nicht unbedingt wissen, dass Sie einen freigegebenen Zeiger daraus erstellen.

void f(std::shared_ptr<int> arg); 
int a; 
f(&a); // bug 

Auch wenn Sie dies außer Acht lassen, erstellen Sie die unsichtbare vorübergehend an der Aufrufstelle und shared_ptr schaffen, ist ziemlich teuer.

+1

Wenn du es nicht mit 'new' erstellst, weil du dann' delete' Speicher hast, hast du nicht 'new'ed. – milleniumbug

+0

@ giò, Es kann Mehrdeutigkeit verursachen, und ein 'shared_ptr' ist teurer, als die meisten Leute möchten, wenn es implizit wäre. Für andere Klassen kann es auch eine nicht offensichtliche Transformation sein. Zum Beispiel würde die Übergabe von "5" und die Umwandlung in einen "std :: vector" sehr unintuitiv sein. – chris

28

Die Syntax:

shared_ptr<Type> var = new Type(); 

Ist copy initialization. Dies ist der Initialisierungstyp für Funktionsargumente.

Wenn dies zulässig wäre, könnten Sie versehentlich einen einfachen Zeiger an eine Funktion übergeben, die einen intelligenten Zeiger verwendet. Darüber hinaus, wenn während der Wartung, jemand void foo(P*) zu void foo(std::shared_ptr<P>) geändert, die genauso gut kompilieren würde, was zu undefiniertem Verhalten führt.

Da diese Operation im Wesentlichen den Besitz eines einfachen Zeigers übernimmt, muss diese Operation explizit ausgeführt werden. Dies ist der Grund, warum der shared_ptr Konstruktor, der einen einfachen Zeiger verwendet, explicit ist - um versehentliche implizite Konvertierungen zu vermeiden.


Die sicherere und effizientere Alternative ist:

auto var = std::make_shared<Type>(); 
+4

@ giò Es ist in der Tat ein großes Problem. –

+1

@ giò Ein gemeinsamer Zeiger geht davon aus, dass er seinen Zeiger besitzt (und ihn nur mit anderen gemeinsam genutzten Zeigern teilt).Wenn Sie einen unformatierten Zeiger an einen freigegebenen Zeiger übergeben haben, besteht die Möglichkeit, dass ein anderer Codecode davon ausging, dass er den unformatierten Zeiger besitzt und versuchen würde, ihn an einem bestimmten Punkt zu löschen. Aber auch der gemeinsame Zeiger! Also ja, doppelte Löschung ist überhaupt nicht gut. – KABoissonneault

8

Ich frage mich, warum sie eine viel einfachere und besser nicht erlauben ...

Ihre Meinung wird sich ändern, wie Sie mehr erfahren werden und mehr schlecht geschrieben, fehlerhaftem Code begegnen. Wie alle Standard-Bibliotheksobjekte ist es so geschrieben, dass es so schwer wie möglich ist, undefiniertes Verhalten zu verursachen (d. H. Schwer zu findende Fehler, die jede Zeit verschwenden und unseren Lebenswillen zerstören).

betrachten:

#include<memory> 

struct Foo {}; 

void do_something(std::shared_ptr<Foo> pfoo) 
{ 
    // ... some things 
} 

int main() 
{ 
    auto p = std::make_shared<Foo>(/* args */); 
    do_something(p.get()); 
    p.reset(); // BOOM! 
} 

Dieser Code nicht kompiliert werden kann, und das ist eine gute Sache. Denn wenn dies der Fall wäre, würde das Programm undefiniertes Verhalten aufweisen.

Dies ist, weil wir das gleiche Foo zweimal löschen würden.

Dieses Programm wird kompiliert und ist wohlgeformt.

#include<memory> 

struct Foo {}; 

void do_something(std::shared_ptr<Foo> pfoo) 
{ 
    // ... some things 
} 

int main() 
{ 
    auto p = std::make_shared<Foo>(/* args */); 
    do_something(p); 
    p.reset(); // OK 
}