it-swarm.com.de

Wie kann unique_ptr keinen Overhead haben, wenn es den Deleter speichern muss?

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, dassshared_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 jedem unique_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 in shared_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!

25
bigxiao

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.

31
Angew

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.

12
MSalters

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.

10
Passer By

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();
}
0
NoSenseEtAl