O código a seguir compila e vincula com Visual Studio
(2017 e 2019 com /permissive-
), mas não compila com gcc
ou 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>(); }
Meu entendimento é que, em main.cpp
, Foo::Bar
deve ser um tipo completo, porque sua exclusão é tentada ~Foo()
, o que é implicitamente declarado e, portanto, implicitamente definido em todas as unidades de tradução que o acessam.
No entanto, Visual Studio
não concorda e aceita esse código. Além disso, descobri que as seguintes alterações Visual Studio
rejeitam o código:
- Tornando
(1)
não virtual - Definindo
(2)
inline -Foo() = default;
ou seja,Foo(){};
- Removendo
(3)
Parece-me Visual Studio
que não define um destruidor implícito em todos os lugares em que é usado nas seguintes condições:
- O destruidor implícito é virtual
- A classe possui um construtor definido em uma unidade de tradução diferente
Em vez disso, parece definir apenas o destruidor na unidade de conversão que também contém a definição para o construtor na segunda condição.
Então agora eu estou me perguntando:
- Isso é permitido?
- É especificado em algum lugar, ou pelo menos conhecido, que
Visual Studio
faz isso?
Atualização: arquivei um relatório de bug https://developercommunity.visualstudio.com/content/problem/790224/implictly-declared-virtual-destructor-does-not-app.html . Vamos ver o que os especialistas acham disso.
struct BarDeleter { void operator()(Bar*) const noexcept; };
e altere o unique_ptr parastd::unique_ptr<Bar, BarDeleter> bar_;
. Em seguida, na unidade de tradução da implementação, adicionevoid Foo::BarDeleter::operator()(Foo::Bar* p) const noexcept { try { delete p; } catch(...) {/*discard*/}}
Respostas:
Eu acredito que isso seja um bug no MSVC. Quanto ao
std::default_delete::operator()
, o Padrão diz que [unique.ptr.dltr.dflt / 4] :Como não há cláusula "não é necessário diagnóstico" , é necessário um compilador C ++ em conformidade para emitir um diagnóstico [intro.compliance / 2.2] :
junto com [introdução / conformidade / 1] :
O GCC usa
static_assert
para diagnosticar a integridade do tipo. O MSVC aparentemente não realiza essa verificação. Se ele passa silenciosamente um parâmetro destd::default_delete::operator()
paradelete
, isso causa um comportamento indefinido . O que pode corresponder à sua observação. Pode funcionar, mas até que seja garantida pela documentação (como uma extensão C ++ não padrão), eu não a usaria.fonte
class Demo { std::vector<int> data; };
std::unique_ptr<Bar>
. O problema está no construtor padrão deFoo
. Se houver uma exceção, seráFoo::Foo()
necessário destruir o subobjeto já construídobar_
(reversão).