Ich benutze das Pimpl-Idiom mit std::unique_ptr
:
class window {
window(const rectangle& rect);
private:
class window_impl; // defined elsewhere
std::unique_ptr<window_impl> impl_; // won't compile
};
Ich erhalte jedoch einen Kompilierungsfehler bezüglich der Verwendung eines unvollständigen Typs in Zeile 304 in <memory>
:
Ungültige Anwendung von '
sizeof
' auf einen unvollständigen Typ 'uixx::window::window_impl
'
Soweit ich weiß, std::unique_ptr
sollte mit einem unvollständigen Typ verwendet werden können. Ist das ein Fehler in libc ++ oder mache ich hier etwas falsch?
Antworten:
Hier sind einige Beispiele für
std::unique_ptr
unvollständige Typen. Das Problem liegt in der Zerstörung.Wenn Sie pimpl mit verwenden
unique_ptr
, müssen Sie einen Destruktor deklarieren:da sonst der Compiler einen Standard generiert und dafür eine vollständige Deklaration benötigt
foo::impl
.Wenn Sie Vorlagenkonstruktoren haben, sind Sie fertig, auch wenn Sie das Element nicht erstellen
impl_
:Im Namespace-Bereich
unique_ptr
funktioniert die Verwendung auch nicht:da der Compiler hier wissen muss, wie man dieses statische Dauerobjekt zerstört. Eine Problemumgehung ist:
quelle
foo::~foo() = default;
in die src-Datei setzenWie Alexandre C. erwähnte, besteht das Problem darin
window
, dass der Destruktor implizit an Stellen definiert wird, an denen der Typwindow_impl
noch unvollständig ist. Zusätzlich zu seinen Lösungen besteht eine weitere Problemumgehung darin, einen Deleter-Funktor im Header zu deklarieren:Beachten Sie, dass Sie eine benutzerdefinierte Deleter Funktion schließt die Verwendung von mit
std::make_unique
(von C ++ 14), wie bereits diskutiert hier .quelle
Verwenden Sie einen benutzerdefinierten Löscher
Das Problem ist, dass
unique_ptr<T>
der DestruktorT::~T()
in seinem eigenen Destruktor, seinem Verschiebungszuweisungsoperator und seinerunique_ptr::reset()
Elementfunktion (nur) aufgerufen werden muss . Diese müssen jedoch in mehreren PIMPL-Situationen (implizit oder explizit) aufgerufen werden (bereits im Destruktor und Verschiebungszuweisungsoperator der äußeren Klasse).Wie bereits in einer anderen Antwort darauf hingewiesen, ein Weg , dies zu vermeiden , sich zu bewegen , alle Operationen , die erfordern
unique_ptr::~unique_ptr()
,unique_ptr::operator=(unique_ptr&&)
undunique_ptr::reset()
in die Quelldatei , in der die Pimpl Helfer Klasse tatsächlich definiert.Dies ist jedoch ziemlich unpraktisch und widerspricht bis zu einem gewissen Grad dem eigentlichen Punkt des Pickel-Idoims. Eine viel sauberere Lösung, die alles vermeidet, einen benutzerdefinierten Löscher zu verwenden und seine Definition nur in die Quelldatei zu verschieben, in der sich die Pickel-Hilfsklasse befindet. Hier ist ein einfaches Beispiel:
Anstelle einer separaten Deleter-Klasse können Sie auch eine freie Funktion oder ein
static
Mitglied vonfoo
in Verbindung mit einem Lambda verwenden:quelle
Wahrscheinlich haben Sie einige Funktionskörper in der .h-Datei innerhalb der Klasse, die einen unvollständigen Typ verwenden.
Stellen Sie sicher, dass Sie in Ihrem .h für Klassenfenster nur eine Funktionsdeklaration haben. Alle Funktionskörper für Fenster müssen sich in der CPP-Datei befinden. Und auch für window_impl ...
Übrigens müssen Sie Ihrer .h-Datei explizit eine Destruktordeklaration für die Windows-Klasse hinzufügen.
Sie können jedoch KEINEN leeren dtor-Text in Ihre Header-Datei einfügen:
Muss nur eine Erklärung sein:
quelle
Um die Antworten des anderen über den benutzerdefinierten Löscher zu ergänzen, habe ich in unserer internen "Dienstprogrammbibliothek" einen Hilfskopf hinzugefügt, um dieses allgemeine Muster zu implementieren (
std::unique_ptr
von einem unvollständigen Typ, der nur einigen TU bekannt ist, um z. B. lange Kompilierungszeiten zu vermeiden oder bereitzustellen nur ein undurchsichtiger Griff für Kunden).Es bietet das allgemeine Gerüst für dieses Muster: eine benutzerdefinierte Löschklasse, die eine extern definierte Löschfunktion aufruft, einen Typalias für a
unique_ptr
mit dieser Löschklasse und ein Makro zum Deklarieren der Löschfunktion in einer TU, die eine vollständige Definition der enthält Art. Ich denke, dass dies einen allgemeinen Nutzen hat, also hier ist es:quelle
Möglicherweise nicht die beste Lösung, aber manchmal können Sie stattdessen shared_ptr verwenden. Wenn es natürlich ein bisschen übertrieben ist, aber ... was unique_ptr betrifft, werde ich vielleicht noch 10 Jahre warten, bis sich C ++ - Standardhersteller dafür entscheiden, Lambda als Deleter zu verwenden.
Andere Seite. Gemäß Ihrem Code kann es vorkommen, dass window_impl in der Zerstörungsphase unvollständig ist. Dies könnte ein Grund für undefiniertes Verhalten sein. Siehe dies: Warum ist das Löschen eines unvollständigen Typs wirklich ein undefiniertes Verhalten?
Wenn möglich, würde ich für alle Ihre Objekte mit einem virtuellen Destruktor ein sehr einfaches Objekt definieren. Und du bist fast gut. Sie sollten nur bedenken, dass das System den virtuellen Destruktor für Ihren Zeiger aufruft, also sollten Sie ihn für jeden Vorfahren definieren. Sie sollten auch Basisklasse in Vererbungsabschnitt als virtuelle (siehe definieren diese für weitere Details).
quelle