Mam trochę kodu w nagłówku, który wygląda następująco:
#include <memory>
class Thing;
class MyClass
{
std::unique_ptr< Thing > my_thing;
};
Jeśli dołączę ten nagłówek do cpp, który nie zawiera Thing
definicji typu, wówczas nie kompiluje się w VS2010-SP1:
1> C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067): błąd C2027: użycie nieokreślonego typu „Rzecz”
Zamień std::unique_ptr
na std::shared_ptr
i kompiluje.
Zgaduję więc, że obecna std::unique_ptr
implementacja VS2010 wymaga pełnej definicji i jest całkowicie zależna od implementacji.
Albo to jest? Czy w jego standardowych wymaganiach jest coś, co uniemożliwia std::unique_ptr
implementacji działanie tylko z deklaracją forward? To dziwne, ponieważ powinno zawierać tylko wskaźnik Thing
, prawda?
shared_ptr
unique_ptr
Howarda Hinnanta i / ” . Tabela na końcu powinna odpowiedzieć na twoje pytanie.Odpowiedzi:
Przyjęto stąd .
Większość szablonów w standardowej bibliotece C ++ wymaga, aby były tworzone z kompletnymi typami. Jednak
shared_ptr
iunique_ptr
to częściowe wyjątki. Niektóre, ale nie wszyscy ich członkowie, mogą być tworzone z niekompletnymi typami. Motywacją do tego jest wspieranie idiomów takich jak pimpl przy użyciu inteligentnych wskaźników i bez ryzyka nieokreślonego zachowania.Nieokreślone zachowanie może wystąpić, gdy masz niekompletny typ i wywołujesz
delete
go:Powyżej jest kodeks prawny. To się skompiluje. Twój kompilator może emitować ostrzeżenie o powyższym kodzie, jak wyżej. Kiedy to nastąpi, prawdopodobnie zdarzą się złe rzeczy. Jeśli masz szczęście, Twój program się zawiesi. Jednak bardziej prawdopodobne jest to, że twój program po cichu wyciek pamięci, ponieważ
~A()
nie zostanie wywołany.Użycie
auto_ptr<A>
w powyższym przykładzie nie pomaga. Nadal masz takie samo niezdefiniowane zachowanie, jakbyś używał surowego wskaźnika.Niemniej jednak używanie niekompletnych klas w niektórych miejscach jest bardzo przydatne! To gdzie
shared_ptr
iunique_ptr
pomoc. Użycie jednego z tych inteligentnych wskaźników pozwoli ci uciec z niekompletnym typem, z wyjątkiem przypadków, gdy konieczne jest posiadanie pełnego typu. A co najważniejsze, gdy konieczne jest posiadanie pełnego typu, pojawia się błąd czasu kompilacji, jeśli spróbujesz użyć inteligentnego wskaźnika z niekompletnym typem w tym momencie.Nigdy więcej nieokreślonego zachowania:
Jeśli Twój kod się kompiluje, to wszędzie tam, gdzie potrzebujesz, używałeś pełnego typu.
shared_ptr
iunique_ptr
wymagają pełnego typu w różnych miejscach. Przyczyny są niejasne, związane z dynamicznym usuwaniem w porównaniu z usuwaniem statycznym. Dokładne powody nie są ważne. W rzeczywistości w większości kodów nie jest tak naprawdę ważne, aby wiedzieć dokładnie, gdzie wymagany jest pełny typ. Po prostu koduj, a jeśli się pomylisz, kompilator powie ci.Jednak w przypadku jest to pomocne dla Ciebie, tutaj znajduje się tabela, która dokumentuje kilku członków
shared_ptr
, aunique_ptr
w odniesieniu do wymogów kompletności. Jeśli element wymaga pełnego typu, wówczas wpis ma „C”, w przeciwnym razie wpis w tabeli jest wypełniony „I”.Wszelkie operacje wymagające konwersji wskaźnika wymagają kompletnych typów dla obu
unique_ptr
ishared_ptr
.unique_ptr<A>{A*}
Konstruktor może uciec z niekompletnąA
tylko wtedy, gdy kompilator nie jest wymagane, aby skonfigurować połączenie do~unique_ptr<A>()
. Na przykład, jeśli umieściszunique_ptr
stos na stosie, możesz uciec z niekompletnymA
. Więcej szczegółów na ten temat można znaleźć w odpowiedzi BarryTheHatchet tutaj .źródło
unique_ptr
jako zmienną składową klasy, po prostu jawnie zadeklaruj destruktor (i konstruktor) w deklaracji klasy (w pliku nagłówkowym) i przejdź do ich zdefiniowania w pliku źródłowym (i umieść nagłówek z pełną deklaracją klasy wskazanej w pliku źródłowym), aby uniemożliwić kompilatorowi automatyczne wstawianie konstruktora lub destruktora w pliku nagłówkowym (co powoduje błąd). stackoverflow.com/a/13414884/368896 również pomaga mi o tym przypomnieć.Kompilator potrzebuje definicji Thing, aby wygenerować domyślny destruktor dla MyClass. Jeśli jawnie zadeklarujesz destruktor i przeniesiesz jego (pustą) implementację do pliku CPP, kod powinien się skompilować.
źródło
MyClass::~MyClass() = default;
w pliku implementacyjnym wydaje się, że mniej prawdopodobne jest, że zostanie przypadkowo usunięty później przez kogoś, kto podejrzewa, że ciało dystrybutora zostało usunięte, a nie celowo pozostawione puste.default
ed idelete
d.MyClass::~MyClass() = default
nie przenosi go do pliku implementacji w Clang. (jeszcze?)To nie zależy od implementacji. Powodem tego jest to, że
shared_ptr
określa właściwy destruktor, który ma zostać wywołany w czasie wykonywania - nie jest to część podpisu typu. Jednakunique_ptr
destruktor jest częścią tego typu i musi być znany w czasie kompilacji.źródło
Wygląda na to, że obecne odpowiedzi nie dokładnie wyjaśniają, dlaczego domyślny konstruktor (lub destruktor) jest problemem, ale puste deklarowane w cpp nie są.
Oto co się dzieje:
Jeśli klasa zewnętrzna (tj. MyClass) nie ma konstruktora lub destruktora, kompilator generuje te domyślne. Problem polega na tym, że kompilator zasadniczo wstawia domyślny pusty konstruktor / destruktor do pliku .hpp. Oznacza to, że kod domyślnego contructor / destructor jest kompilowany wraz z plikiem binarnym pliku wykonywalnego hosta, a nie wraz z plikami binarnymi biblioteki. Jednak te definicje nie mogą tak naprawdę konstruować klas częściowych. Kiedy więc linker wchodzi do pliku binarnego biblioteki i próbuje uzyskać konstruktor / destruktor, nie może go znaleźć i pojawia się błąd. Jeśli kod konstruktora / destruktora znajdował się w twoim pliku .cpp, to plik binarny biblioteki ma ten dostępny do łączenia.
Nie ma to nic wspólnego z używaniem unikalnych_ptrów lub współużytkowanych_ptrów, a inne odpowiedzi wydają się być mylącym błędem w starym VC ++ dla implementacji unikatowych_ptr (VC ++ 2015 działa dobrze na moim komputerze).
Morał tej historii jest taki, że twój nagłówek musi pozostać wolny od definicji konstruktora / destruktora. Może zawierać tylko ich deklarację. Na przykład
~MyClass()=default;
w hpp nie będzie działać. Jeśli zezwolisz kompilatorowi na wstawienie domyślnego konstruktora lub destruktora, pojawi się błąd linkera.Jeszcze jedna uwaga: jeśli nadal pojawia się ten błąd, nawet jeśli w pliku cpp znajduje się konstruktor i destruktor, najprawdopodobniej przyczyną jest niewłaściwa kompilacja biblioteki. Na przykład, pewnego razu po prostu zmieniłem typ projektu z Konsoli na Bibliotekę w VC ++ i dostałem ten błąd, ponieważ VC ++ nie dodał symbolu preprocesora _LIB, co spowodowało wyświetlenie tego samego komunikatu o błędzie.
źródło
Dla kompletności:
Nagłówek: Ah
Źródło A.cpp:
Definicja klasy B musi być postrzegana przez konstruktor, destruktor i wszystko, co może domyślnie usunąć B. (Chociaż konstruktor nie pojawia się na powyższej liście, nawet w VS2017 nawet konstruktor potrzebuje definicji B. A to ma sens, biorąc pod uwagę że w przypadku wyjątku w konstruktorze unikalna_ptr jest ponownie niszczona).
źródło
Pełna definicja Rzeczy jest wymagana w momencie tworzenia szablonu. To jest dokładnie powód, dla którego kompiluje się idiom pimpl.
Gdyby to nie było możliwe, ludzie nie będą zadawać pytania, takie jak ten .
źródło
Prostą odpowiedzią jest użycie zamiast tego shared_ptr.
źródło
Jak dla mnie,
Wystarczy dołączyć nagłówek ...
źródło