Schauen Sie sich zunächst an, was C++ Primer über unique_ptr
und shared_ptr
gesagt hat:
$ 16.1.6. Effizienz und Flexibilität
Wir können sicher sein, dass
shared_ptr
den Löscher nicht als direktes Mitglied enthält, weil der Typ des Löschers erst zur Laufzeit bekannt ist.Da der Typ des Deleters Teil des Typs eines
unique_ptr
ist, ist der Typ des Deleter-Members zur Kompilierungszeit bekannt. Der Deleter kann direkt in jedemunique_ptr
Objekt gespeichert werden.
Es sieht also so aus, als ob shared_ptr
kein direktes Mitglied von deleter hat, unique_ptr
jedoch. Allerdings die am häufigsten gewählte Antwort auf eine andere Frage sagt:
Wenn Sie den Deleter als Template-Argument angeben (wie in
unique_ptr
), ist er Teil des Typs und Sie müssen in den Objekten dieses Typs keine zusätzlichen Informationen speichern . Wenn deleter als Konstruktorargument übergeben wird (wie inshared_ptr
) müssen Sie es im Objekt speichern . Dies ist der Preis für zusätzliche Flexibilität, da Sie für Objekte desselben Typs unterschiedliche Deleter verwenden können.
Der zwei zitierte Absatz ist völlig widersprüchlich, was mich verwirrt. Was mehr ist, viele Leute sagen, dass unique_ptr
kein Overhead ist weil es den Deleter nicht als Mitglied speichern muss. Wie wir jedoch wissen, hat unique_ptr
einen Konstruktor für unique_ptr<obj,del> p(new obj,fcn)
, was bedeutet, dass wir einen Deleter an ihn übergeben können, so dass unique_ptr
Deleter anscheinend als Mitglied gespeichert hat. Was für ein Chaos!
std::unique_ptr<T>
ist wahrscheinlich Null-Overhead (bei jeder Standard-Bibliotheksimplementierung). std::unique_ptr<T, D>
für eine beliebige D
ist im Allgemeinen kein Null-Overhead.
Der Grund ist einfach: Mit der Empty-Base-Optimierung können Sie die Speicherung des Deleters im Falle eines leeren (und damit statuslosen) Typs (z. B. std::default_delete
-Instantiierungen) aufheben.
Die Schlüsselphrase, die Sie zu verwirren scheint, lautet "Der Deleter can kann direkt gespeichert werden". Es ist jedoch sinnlos, einen Deleter vom Typ std::default_delete
zu speichern. Wenn Sie einen benötigen, können Sie einfach einen als std::default_delete{}
erstellen.
Im Allgemeinen müssen zustandslose Deleter nicht gespeichert werden, da Sie sie bei Bedarf erstellen können.
Angews Antwort erklärte ziemlich gründlich, was los ist.
Für die Neugierigen, wie die Dinge unter der Decke aussehen könnten
template<typename T, typename D, bool Empty = std::is_empty_v<D>>
class unique_ptr
{
T* ptr;
D d;
// ...
};
template<typename T, typename D>
class unique_ptr<T, D, true> : D
{
T* ptr;
// ...
};
Was sich auf leere Deleter spezialisiert hat und die Leerbasisoptimierung nutzt.
Kurze Einführung:
unique_ptr can führt einen kleinen Overhead ein, aber nicht wegen des Deleters, sondern weil der Wert auf null gesetzt werden muss. Wenn Sie rohe Zeiger verwenden, können Sie den alten Zeiger im fehleranfälligen, aber legitimen Zustand belassen wo es noch auf den Punkt zeigt, auf den es zuvor gezeigt hat. Offensichtlich kann der intelligente Optimierer optimieren, was jedoch nicht garantiert werden kann.
Zurück zum Deleter:
Andere Antworten sind richtig, aber ausführlich. Hier ist also die vereinfachte Version ohne Erwähnung von EBO oder anderen komplizierten Begriffen.
Wenn deleter leer ist (keinen Status hat), müssen Sie es nicht im unique_ptr aufbewahren. Wenn Sie es brauchen, können Sie es einfach bauen, wenn Sie es brauchen. Alles, was Sie wissen müssen, ist der Deleter-Typ (und das ist eines der Vorlagenargumente für unique_ptr).
Betrachten Sie zum Beispiel folgenden Code, der auch die einfache Erstellung eines statuslosen Objekts auf Anforderung veranschaulicht.
#include <iostream>
#include <string>
#include <string_view>
template<typename Person>
struct Greeter{
void greet(){
static_assert(std::is_empty_v<Person>, "Person must be stateless");
Person p; // Stateless Person instance constructed on demand
std::cout << "Hello " << p() << std::endl;
}
// ... and not kept as a member.
};
struct Bjarne{
std::string_view operator()(){
return "Bjarne";
}
};
int main() {
Greeter<Bjarne> hello_bjarne;
hello_bjarne.greet();
}