Kann ein Compiler die Implementierung eines implizit deklarierten virtuellen Destruktors in einer einzelnen separaten Übersetzungseinheit platzieren?

8

Der folgende Code kompiliert und verknüpft mit Visual Studio(sowohl 2017 als auch 2019 mit /permissive-), kompiliert jedoch nicht mit entweder gccoder clang.

foo.h

#include <memory>

struct Base {
    virtual ~Base() = default; // (1)
};

struct Foo : public Base {
    Foo();                     // (2)
    struct Bar;
    std::unique_ptr<Bar> bar_;
};

foo.cpp

#include "foo.h"

struct Foo::Bar {};            // (3)
Foo::Foo() = default;

main.cpp

#include "foo.h"

int main() {
    auto foo = std::make_unique<Foo>();
}

Mein Verständnis ist , dass, in main.cpp, Foo::Barmuß eine vollständige Typ sein, weil deren Löschung in versucht wird ~Foo(), die implizit deklariert und somit implizit in jeder Übersetzungseinheit definiert , die es zugreift.

Ist Visual Studiojedoch nicht einverstanden und akzeptiert diesen Code. Außerdem habe ich festgestellt, dass die folgenden Änderungen dazu führen, dass Visual Studioder Code abgelehnt wird:

  • Herstellung von (1)nicht-virtuellen
  • (2)Inline definieren - dh Foo() = default;oderFoo(){};
  • Entfernen (3)

Es sieht für mich so aus Visual Studio ein impliziter Destruktor nicht überall dort definiert, wo er unter den folgenden Bedingungen verwendet wird:

  • Der implizite Destruktor ist virtuell
  • Die Klasse verfügt über einen Konstruktor, der in einer anderen Übersetzungseinheit definiert ist

Stattdessen scheint es nur den Destruktor in der Übersetzungseinheit zu definieren, der auch die Definition für den Konstruktor in der zweiten Bedingung enthält.

Jetzt frage ich mich also:

  • Ist das erlaubt?
  • Ist irgendwo angegeben oder zumindest bekannt, dass dies der Visual StudioFall ist?

Update: Ich habe einen Fehlerbericht https://developercommunity.visualstudio.com/content/problem/790224/implictly-declared-virtual-destructor-does-not-app.html eingereicht . Mal sehen, was die Experten daraus machen.

Kennzeichen
quelle
1
Was passiert, wenn Sie den Code mit Visual Studio mit dem Schalter / permissive- erstellen ?
Jesper Juhl
1
Gleiches Ergebnis. Ich werde das in die Frage stellen.
Mark
1
Die Änderungen 2 und 3 sind klar. Sie benötigen einen vollständigen Typ, wenn (Standard-) Deleter aufgerufen wird (im Destruktor von unique_ptr, was wiederum im Konstruktor von Foo vorkommt. Wenn letzterer inline ist, muss der Typ bereits im Header vollständig sein). Änderung 1 überrascht mich allerdings keine Erklärung dafür.
Aconcagua
Fügen Sie dies zu Foo hinzu: struct BarDeleter { void operator()(Bar*) const noexcept; };und ändern Sie den unique_ptr in std::unique_ptr<Bar, BarDeleter> bar_;. void Foo::BarDeleter::operator()(Foo::Bar* p) const noexcept { try { delete p; } catch(...) {/*discard*/}}
Fügen Sie

Antworten:

2

Ich glaube, das ist ein Fehler in MSVC. Wie für das std::default_delete::operator(), sagt die Norm , dass [unique.ptr.dltr.dflt / 4] :

Anmerkungen: Wenn T ein unvollständiger Typ ist, ist das Programm fehlerhaft .

Da es keine Klausel "Keine Diagnose erforderlich" gibt , muss ein konformer C ++ - Compiler eine Diagnose ausgeben [intro.compliance / 2.2] :

Wenn ein Programm einen Verstoß gegen eine diagnostizierbare Regel oder ... enthält, muss eine konforme Implementierung mindestens eine Diagnosemeldung ausgeben .

zusammen mit [Intro / Compliance / 1] :

Der Satz diagnostizierbarer Regeln besteht aus allen syntaktischen und semantischen Regeln in diesem Dokument, mit Ausnahme der Regeln, die eine explizite Notation enthalten, dass „keine Diagnose erforderlich ist“ oder die als „undefiniertes Verhalten“ bezeichnet werden.


GCC verwendet, static_assertum die Typvollständigkeit zu diagnostizieren. MSVC führt anscheinend keine solche Prüfung durch. Wenn ein Parameter von std::default_delete::operator()to stillschweigend übergeben wird, führt deletedies zu undefiniertem Verhalten . Welches könnte mit Ihrer Beobachtung korrespondieren. Es mag funktionieren, aber bis es durch die Dokumentation (als nicht standardmäßige C ++ - Erweiterung) garantiert wird, würde ich es nicht verwenden.

Daniel Langr
quelle
Dies ist bis jetzt auch meine Argumentation.
Mark
1
@DanielLangr OHHHH [@ $% * & +!] , Völlig übersehen, dann ist es der Standard Konstruktor , die bereitgestellt wird, nicht die destructor !!! Ein virtueller Destruktor in der Basisklasse hat mich bereits vertrieben ... Entschuldigung . Dann haben Sie natürlich vollkommen recht. Ich frage mich jetzt, ob die Bereitstellung von ctor anstelle von dtor absichtlich oder zufällig war ...
Aconcagua
1
@Aconcagua, es ist absichtlich. Das Problem ist, dass msvc den impliziten Destruktor in einer Übersetzungseinheit definiert, in der er nicht verwendet wird, und ihn nicht in einer Übersetzungseinheit definiert, in der er verwendet wird.
Mark
1
@ DanielLangr Danke für den Link ... Aber es ist auch für den Standardkonstruktor sinnvoll, betrachten Sie einfach eine Klasse mit komplexen Mitgliedern:class Demo { std::vector<int> data; };
Aconcagua
1
@Aconcagua Ich glaube ich verstehe endlich. Das Problem liegt nicht beim Standardkonstruktor von std::unique_ptr<Bar>. Das Problem ist mit dem Standardkonstruktor von Foo. Wenn es eine Ausnahme gibt, Foo::Foo()muss das bereits erstellte Unterobjekt zerstört werden bar_(Rollback).
Daniel Langr