Czy std:: unique ptr wymaga znajomości pełnej definicji t?

Mam jakiś kod w nagłówku, który wygląda tak:

#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 definicji typu Thing, to nie będzie kompilowany pod VS2010-SP1:

1>C:\Program Files (x86) \ Microsoft Visual Studio 10.0\VC\include\memory(2067): error c2027: use of undefined type 'Thing'

Zastąp std::unique_ptr przez std::shared_ptr i kompiluje.

Zgaduję więc, że to obecna implementacja VS2010 std::unique_ptr, która wymaga pełnej definicji i jest całkowicie zależna od implementacji.

Czy jest? Czy w standardowych wymaganiach jest coś, co uniemożliwia implementację std::unique_ptr tylko z deklaracją forward? Wydaje się to dziwne, ponieważ powinno trzymać tylko wskaźnik do Thing, prawda?

Author: JasonMArcher, 2011-05-16

7 answers

Przyjęty z tutaj .

Większość szablonów w bibliotece standardowej C++ wymaga utworzenia instancji z kompletnymi typami. Jednak shared_ptr i unique_ptrczęściowymi wyjątkami. Niektóre, ale nie wszystkie z ich członków mogą być utworzone z niekompletnymi typami. Motywacją do tego jest wspieranie idiomów takich jak pimpl za pomocą inteligentnych wskaźników i bez ryzyka niezdefiniowanego zachowania.

Nieokreślone zachowanie może wystąpić, gdy masz niekompletny Typ i dzwonisz delete na to:

class A;
A* a = ...;
delete a;

Powyższe jest kodeksem prawnym. Skompiluje się. Twój kompilator może, ale nie musi, emitować ostrzeżenie dla powyższego kodu, jak wyżej. Kiedy to nastąpi, prawdopodobnie wydarzy się coś złego. Jeśli masz szczęście, twój program się zawiesi. Jednak bardziej prawdopodobnym rezultatem jest to, że twój program po cichu wycieka pamięć, ponieważ ~A() nie zostanie wywołany.

Użycie auto_ptr<A> w powyższym przykładzie nie pomaga. Nadal masz takie samo nieokreślone zachowanie, jak gdybyś użył raw pointer.

Niemniej jednak używanie niekompletnych klas w niektórych miejscach jest bardzo przydatne! Tutaj shared_ptr i unique_ptr pomagają. Użycie jednego z tych inteligentnych wskaźników pozwoli Ci uniknąć niekompletnego typu, z wyjątkiem przypadków, w których konieczne jest posiadanie kompletnego typu. A co najważniejsze, gdy konieczne jest posiadanie kompletnego typu, pojawia się błąd w czasie kompilacji, jeśli spróbujesz użyć inteligentnego wskaźnika z niekompletnym typem w tym momencie.

No more undefined zachowanie:

Jeśli twój kod jest kompilowany, To używasz kompletnego typu wszędzie, gdzie potrzebujesz.

class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};

shared_ptr i unique_ptr wymagają kompletnego typu w różnych miejscach. Przyczyny są niejasne, mając do czynienia z deleter dynamiczny vs Deleter statyczny. Dokładne powody nie są ważne. W rzeczywistości w większości kodu nie jest tak naprawdę ważne, abyś wiedział dokładnie, gdzie wymagany jest kompletny typ. Wystarczy kod, a jeśli się pomylisz, kompilator powie ty.

Jednak, jeśli jest to dla ciebie pomocne, oto tabela, która dokumentuje kilku członków shared_ptr i unique_ptr w odniesieniu do wymogów kompletności. Jeśli element wymaga typu pełnego, to wpis ma "C", w przeciwnym razie wpis w tabeli jest wypełniony "I".

Complete type requirements for unique_ptr and shared_ptr

                            unique_ptr       shared_ptr
+------------------------+---------------+---------------+
|          P()           |      I        |      I        |
|  default constructor   |               |               |
+------------------------+---------------+---------------+
|      P(const P&)       |     N/A       |      I        |
|    copy constructor    |               |               |
+------------------------+---------------+---------------+
|         P(P&&)         |      I        |      I        |
|    move constructor    |               |               |
+------------------------+---------------+---------------+
|         ~P()           |      C        |      I        |
|       destructor       |               |               |
+------------------------+---------------+---------------+
|         P(A*)          |      I        |      C        |
+------------------------+---------------+---------------+
|  operator=(const P&)   |     N/A       |      I        |
|    copy assignment     |               |               |
+------------------------+---------------+---------------+
|    operator=(P&&)      |      C        |      I        |
|    move assignment     |               |               |
+------------------------+---------------+---------------+
|        reset()         |      C        |      I        |
+------------------------+---------------+---------------+
|       reset(A*)        |      C        |      C        |
+------------------------+---------------+---------------+

Wszelkie operacje wymagające konwersji wskaźnika wymagają kompletnych typów zarówno dla unique_ptr, jak i shared_ptr.

Konstruktor unique_ptr<A>{A*} może ujść na sucho z niekompletnym A tylko wtedy, gdy kompilator nie wymagane do skonfigurowania połączenia do ~unique_ptr<A>(). Na przykład, jeśli umieścisz unique_ptr na stosie, możesz uciec z niekompletnym A. Więcej szczegółów na ten temat można znaleźć w BarryTheHatchet ' Sodpowiedź tutaj.

 279
Author: Howard Hinnant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:17

Kompilator potrzebuje definicji rzeczy do wygenerowania domyślnego destruktora dla MyClass. Jeśli jawnie zadeklarujesz Destruktor i przeniesiesz jego (pustą) implementację do pliku CPP, kod powinien zostać skompilowany.

 37
Author: Igor Nazarenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-16 00:17:11

To nie jest zależne od implementacji. To działa, ponieważ shared_ptr określa właściwy Destruktor do wywołania w czasie wykonywania-nie jest on częścią sygnatury typu. Jednak Destruktor unique_ptr jest częścią jego typu I musi być znany w czasie kompilacji.

 9
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-01 07:07:06

Wygląda na to, że obecne odpowiedzi nie do końca wyjaśniają, dlaczego domyślny konstruktor (lub Destruktor) jest problemem, ale puste zadeklarowane w cpp nie są.

Oto co się dzieje:

Jeśli Klasa zewnętrzna (np. MyClass) nie posiada konstruktora ani destruktora, kompilator generuje domyślne. Problem z tym polega na tym, że kompilator zasadniczo wstawia domyślny pusty konstruktor/Destruktor do .plik hpp. Oznacza to, że kod dla domyślnego contructor / destructor otrzymuje skompilowany wraz z binarnym plikiem wykonywalnym hosta, a nie wraz z binarnymi bibliotekami. Jednak ta definicja nie może tak naprawdę konstruować klas częściowych. Więc kiedy linker wchodzi do binarnej biblioteki i próbuje uzyskać constructor/destructor, nie znajduje żadnego i dostajesz błąd. Jeśli kod konstruktora / destruktora był w Twoim.cpp wtedy Twoja biblioteka binarna ma dostęp do linkowania.

Więc nie ma to nic wspólnego z używaniem unique_ptr zamiast shared_ptr dla powyższego scenariusza tak długo, jak długo ponieważ używasz nowoczesnych kompilatorów (Stary kompilator VC++ może mieć błąd w implementacji unique_ptr, ale VC++ 2015 działa dobrze na mojej maszynie).

Morał tej historii jest taki, że Twój nagłówek musi pozostać wolny od definicji konstruktora/destruktora. Może zawierać jedynie ich deklarację. Na przykład, ~MyClass()=default; w hpp nie będzie działać. Jeśli pozwolisz kompilatorowi wstawić domyślny konstruktor lub Destruktor, pojawi się błąd linkera.

Jeszcze jedna uwaga: jeśli nadal otrzymujesz ten błąd nawet jeśli masz konstruktor i destruktor w pliku cpp, najprawdopodobniej powodem jest to, że Twoja biblioteka nie jest poprawnie skompilowana. Na przykład, kiedyś 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, a to spowodowało dokładnie ten sam komunikat o błędzie.

 3
Author: ShitalShah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-10 11:56:26

Pełna definicja rzeczy jest wymagana w momencie tworzenia szablonu. Z tego właśnie powodu kompiluje się idiom pimpl.

Gdyby to nie było możliwe, ludzie nie zadawaliby takich pytań jak to .

 1
Author: BЈовић,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:17

Tylko dla kompletności:

Header: A. h

class B; // forward declaration

class A
{
    std::unique_ptr<B> ptr_;  // ok!  
public:
    A();
    ~A();
    // ...
};

Źródło A.cpp:

class B {  ...  }; // class definition

A::A() { ... }
A::~A() { ... }

Definicja klasy B musi być widziana przez konstruktor, Destruktor i wszystko, co mogłoby pośrednio usunąć B. (Chociaż konstruktor nie pojawia się na powyższej liście, w VS2017 nawet konstruktor potrzebuje definicji B. i ma to sens, biorąc pod uwagę, że w przypadku wyjątku w konstruktorze unique_ptr jest ponownie niszczony.)

 0
Author: Joachim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-12 15:31:48

Jak dla mnie,

QList<QSharedPointer<ControllerBase>> controllers;

Po prostu dołącz nagłówek ...

#include <QSharedPointer>
 -1
Author: Sanbrother,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-22 06:37:14