Erfordert der dtor von shared_ptr die Verwendung eines "Deleters"?

Es ist weit bekannt dass Sie eine verwenden könnenshared_ptr zum Speichern eines Zeigers auf einen unvollständigen Typ, solange der Zeiger während der Erstellung der Datei gelöscht werden kann (mit genau definiertem Verhalten)shared_ptr. Zum Beispiel die PIMPL-Technik:

struct interface
{
    interface();                 // out-of-line definition required
    ~interface() = default;   // public inline member, even if implicitly defined
    void foo();
private:
    struct impl;                 // incomplete type
    std::shared_ptr<impl> pimpl; // pointer to incomplete type
};

[main.cpp]

int main()
{
    interface i;
    i.foo();
}

[interface.cpp]

struct interface::impl
{
    void foo()
    {
        std::cout << "woof!\n";
    }
};

interface::interface()
    : pimpl( new impl ) // `delete impl` is well-formed at this point
{}

void interface::foo()
{
    pimpl->foo();
}

Das funktioniert als"Objekt löschen" "Eigentümer Objekt" (*) wird während des Aufbaus der erstelltshared_ptr impimpl( new impl )und nach dem Löschen des Typs in dershared_ptr. Dieses "Besitzerobjekt" wird später verwendet, um das Objekt zu zerstören, auf das verwiesen wird. Aus diesem Grund sollte es sicher sein, eine zu liefernin der Reihe Zerstörer voninterface.

Frage: Wo garantiert der Standard, dass es sicher ist?

(*) Kein Deleter im Sinne des Standards, siehe unten, aber er ruft entweder den benutzerdefinierten Deleter auf oder ruft den Löschausdruck auf. Dieses Objekt wird normalerweise als Teil des Buchhaltungsobjekts gespeichert, wobei die Typlöschung angewendet und der benutzerdefinierte Lösch- / Löschausdruck in einer virtuellen Funktion aufgerufen wird. Zu diesem Zeitpunkt sollte der Löschausdruck ebenfalls gut geformt sein.

Verweis auf den neuesten Entwurf im Github-Repository (94c8fc71, überarbeitet N3797), [util.smartptr.shared.const]

template<class Y> explicit shared_ptr(Y* p);

3 Benötigt:p soll konvertierbar sein zuT*. Y soll ein vollständiger Typ sein. Der Ausdruckdelete p muss gut geformt sein, muss ein klares Verhalten haben und darf keine Ausnahmen werfen.

4 Effekte: Konstruiert ashared_ptr Objekt, das den Zeiger besitztp.

5 Nachbedingungen:use_count() == 1 && get() == p.

6 Würfe:bad_allocoder eine implementierungsdefinierte Ausnahme, wenn eine andere Ressource als Speicher nicht abgerufen werden konnte.

Hinweis: Für diesen ctor,shared_ptr Es ist nicht erforderlich, einen Deleter zu besitzen. Durchdeleterscheint der Standard zu bedeutenbenutzerdefinierte Deleter, wie Sie sie während der Konstruktion als zusätzlichen Parameter angeben (oder dieshared_ptr erwirbt / teilt eines von anderenshared_ptr, z.B. durch Vervielfältigung). Siehe auch (siehe auch [util.smartptr.shared.const] / 9). Die Implementierungen (boost, libstdc ++, MSVC und ich denke, jede vernünftige Implementierung) speichern immer ein "Eigentümerobjekt".

Als eindeleter ist einbenutzerdefinierte Deleter, der Zerstörer vonshared_ptr ist definiert alsdelete (delete-expression) falls es kein benutzerdefiniertes Deleter gibt:

[util.smartptr.shared.dest]

~shared_ptr();

1 Effekte:

Ob*this istleeren oder teilt das Eigentum mit einem anderenshared_ptr Instanz (use_count() > 1) gibt es keine Nebenwirkungen.Ansonsten, wenn*this besitzt ein Objektp und ein Deleterd, d(p) wird genannt.Andernfalls,*this besitzt ein Zeigerp, unddelete p wird genannt.

Ich nehme das anAbsicht ist, dass eine Implementierung erforderlich ist, um den gespeicherten Zeiger auch dann korrekt zu löschen, wenn im Geltungsbereich dershared_ptr dtor, der Löschausdruck ist falsch oder würde UB aufrufen. (Der Löschausdruck muss wohlgeformt sein und ein klar definiertes Verhalten im ctor haben.) Die Frage ist also

Frage: Wo ist das erforderlich?

(Oder bin ich einfach zu wählerisch und es ist irgendwie offensichtlich, dass die Implementierungen erforderlich sind, um ein "Eigentümerobjekt" zu verwenden?)

Antworten auf die Frage(1)

Ihre Antwort auf die Frage