Derzeit lese ich den Quellcode von Protocol Buffer
und habe einen seltsamen enum
Code gefunden, der hier definiert ist
~scoped_ptr() {
enum { type_must_be_complete = sizeof(C) };
delete ptr_;
}
void reset(C* p = NULL) {
if (p != ptr_) {
enum { type_must_be_complete = sizeof(C) };
delete ptr_;
ptr_ = p;
}
}
Warum enum { type_must_be_complete = sizeof(C) };
ist das hier definiert? was wird es verwendet?
ptr_
imsizeof
as alssizeof(*ptr_)
verwendensizeof(C)
.Antworten:
Dieser Trick vermeidet UB, indem sichergestellt wird, dass die Definition von C verfügbar ist, wenn dieser Destruktor kompiliert wird. Andernfalls schlägt die Kompilierung fehl, da der
sizeof
unvollständige Typ (vorwärts deklarierte Typen) nicht bestimmt werden kann, aber die Zeiger verwendet werden können.In der kompilierten Binärdatei würde dieser Code optimiert und hätte keine Auswirkung.
Beachten Sie Folgendes : Das Löschen eines unvollständigen Typs kann ein undefiniertes Verhalten ab 5.3.5 / 5: sein.
g++
gibt sogar die folgende Warnung aus:quelle
sizeof(C)
schlägt beim Kompilieren fehl , wennC
es sich nicht um einen vollständigen Typ handelt. Wenn Sie einen lokalen Bereich festlegenenum
, wird die Anweisung zur Laufzeit harmlos.Auf diese Weise schützt sich der Programmierer vor sich selbst: Das Verhalten eines nachfolgenden
delete ptr_
auf einen unvollständigen Typ ist undefiniert, wenn er einen nicht trivialen Destruktor hat.quelle
delete
? Und wenn ja, warum fängt der Compiler es überhaupt nicht ab?C = void
? WennC
es sich nur um einen undefinierten Typ handeln würde, würde diedelete
Anweisung dann nicht bereits fehlschlagen?Um das zu verstehen
enum
, betrachten Sie zunächst den Destruktor ohne ihn:wo
ptr_
ist einC*
. Wenn der TypC
zu diesem Zeitpunkt unvollständig ist, dh der Compiler nur weißstruct C;
, dass (1) für die C-Instanz, auf die verwiesen wird , ein standardmäßig generierter Nichtstun-Destruktor verwendet wird. Es ist unwahrscheinlich, dass dies für ein Objekt, das von einem intelligenten Zeiger verwaltet wird, richtig ist.Wenn das Löschen über einen Zeiger auf einen unvollständigen Typ immer ein undefiniertes Verhalten hatte, könnte der Standard nur verlangen, dass der Compiler es diagnostiziert und fehlschlägt. Aber es ist genau definiert, wenn der eigentliche Destruktor trivial ist: Wissen, das der Programmierer haben kann, der Compiler jedoch nicht. Warum die Sprache dies definiert und zulässt, ist mir ein Rätsel, aber C ++ unterstützt viele Praktiken, die heute nicht als Best Practices angesehen werden.
Ein vollständiger Typ hat eine bekannte Größe und wird daher
sizeof(C)
genau dann kompiliert, wennC
es sich um einen vollständigen Typ handelt - mit bekanntem Destruktor. So kann es als Wache verwendet werden. Ein Weg wäre einfachIch würde vermuten, dass der Compiler mit einigen Compilern und Optionen diese optimiert, bevor er feststellen kann, dass sie nicht kompiliert werden sollten und dass dies
enum
ein Weg ist, um ein solches nicht konformes Compilerverhalten zu vermeiden:Eine alternative Erklärung für die Wahl
enum
eines Ausdrucks und nicht nur eines verworfenen Ausdrucks ist einfach die persönliche Präferenz.Oder wie James T. Hugget in einem Kommentar zu dieser Antwort vorschlägt : „Die Aufzählung kann eine Möglichkeit sein, beim Kompilieren eine pseudo-portable Fehlermeldung zu erstellen.“
(1) Der standardmäßig generierte Do-Nothing-Destruktor für einen unvollständigen Typ war ein Problem mit old
std::auto_ptr
. Es war so heimtückisch, dass es seinen Weg in einen GOTW-Artikel über die PIMPL-Sprache fand , der vom Vorsitzenden des internationalen C ++ - Standardisierungskomitees Herb Sutter verfasst wurde. Natürlich wird man heutzutage, da diesstd::auto_ptr
veraltet ist, stattdessen einen anderen Mechanismus verwenden.quelle
sizeof(T)
für unvollständige Typen 0 ausgewertet haben, anstatt die Kompilierung fehlzuschlagen. Dies ist jedoch ein nicht konformes Verhalten. (2) Seit C ++ 11static_assert((sizeof(T) > 0), "T must be a complete type");
wäre die Verwendung eine überlegene (und idiomatische) Lösung.sizeof(T)
für unvollständige Typen auf 0 ausgewertet" hat.static_assert(sizeof(T) > 0, "…");
in ihren jeweiligen Implementierungenstd::unique_ptr
um sicherzustellen , dass die Art abgeschlossen ist ...sizeof(T)
in einem booleschen Kontext genau dem Testen entsprichtsizeof(T) > 0
, spielt es keine Rolle, außer vielleicht aus ästhetischen Gründen.Vielleicht ist ein Trick definiert, um sicher zu sein
C
.quelle