Требуется ли std:: unique ptr знать полное определение T?


у меня есть код в заголовке, который выглядит так:

#include <memory>

class Thing;

class MyClass
{
    std::unique_ptr< Thing > my_thing;
};

если я включаю этот заголовок в cpp, который не включает Thing определение типа, то это не компилируется под VS2010-SP1:

1>C:Program файлы (x86)Microsoft Визуальная Студия 10.0VCincludememory(2067): ошибка C2027: использование неопределенного типа 'Thing'

заменить std::unique_ptr by std::shared_ptr и он компилирует.

Итак, я предполагаю, что это текущая VS2010 std::unique_ptrреализация, которая требует полного определения, и это полностью зависит от реализации.

или это? Есть ли что-то в его стандартных требованиях, что делает невозможным для std::unique_ptrреализация для работы только с прямым объявлением? Это кажется странным, так как он должен содержать только указатель на Thing, не так ли?

7 207

7 ответов:

приняла от здесь.

большинство шаблонов в стандартной библиотеке C++ требуют, чтобы они были созданы с полными типами. Однако shared_ptr и unique_ptr are частичная исключения. Некоторые, но не все их члены могут быть созданы с неполными типами. Мотивация для этого заключается в поддержке идиом, таких как интеллекту использование интеллектуальных указателей, и без риска неопределенного поведения.

неопределенное поведение происходят, когда у вас есть неполный тип и вы вызываете delete на:

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

выше уложения. Он будет компилироваться. Ваш компилятор может выдавать или не выдавать предупреждение для вышеуказанного кода, как указано выше. Когда он выполняется, плохие вещи, вероятно, произойдет. Если вам очень повезет, ваша программа рухнет. Однако более вероятным результатом является то, что ваша программа будет молча утечка памяти как ~A() не назовешь.

используя auto_ptr<A> в приведенном выше примере не помогло. Ты все еще ... получите такое же неопределенное поведение, как если бы вы использовали исходный указатель.

тем не менее, использование неполных классов в некоторых местах очень полезно! Вот где shared_ptr и unique_ptr помочь. Использование одного из этих интеллектуальных указателей позволит вам уйти с неполным типом, за исключением случаев, когда необходимо иметь полный тип. И самое главное, когда необходимо иметь полный тип, вы получаете ошибку времени компиляции, если вы пытаетесь использовать интеллектуальный указатель с неполным типом в этот момент.

нет больше неопределенного поведения:

если ваш код компилируется, то вы использовали полный тип везде нужно.

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

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

shared_ptr и unique_ptr требуется полный тип в разных местах. Причины неясны, имея дело с динамическим делетером против статического делетера. Точные причины не важны. На самом деле, в большинстве кодов для вас не очень важно точно знать, где требуется полный тип. Просто код, и если вы ошибаетесь, компилятор скажет вам.

однако, в случае, если это полезно для вас, Вот таблица, которая документирует несколько членов shared_ptr и unique_ptr в отношении требований к полноте. Если член требует полного типа, то запись имеет "C", в противном случае запись таблицы заполняется"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        |
+------------------------+---------------+---------------+

любые операции, требующие преобразования указателя требуют полную типов unique_ptr и shared_ptr.

в unique_ptr<A>{A*} конструктор может уйти с неполным A только если компилятор не требуется для настройки вызова ~unique_ptr<A>(). Например, если вы ставите unique_ptr в куче, вы можете уйти с неполным A. Более подробную информацию по этому вопросу можно найти в BarryTheHatchet это ответ здесь.

компилятор нуждается в определении вещи для создания деструктора по умолчанию для MyClass. Если вы явно объявляете деструктор и перемещаете его (пустую) реализацию в файл CPP, код должен компилироваться.

это не зависит от реализации. Причина, по которой это работает, заключается в том, что shared_ptr определяет правильный деструктор для вызова во время выполнения-он не является частью сигнатуры типа. Однако,unique_ptr'ы деструктор - это часть его тип, и он должен быть известен во время компиляции.

похоже, что текущие ответы точно не прибивают, почему конструктор по умолчанию (или деструктор) является проблемой, но пустые, объявленные в cpp, не являются.

вот что происходит:

Если внешний класс (т. е. MyClass) не имеет конструктора или деструктора, то компилятор генерирует стандартные. Проблема с этим заключается в том, что компилятор по существу вставляет пустой конструктор/деструктор по умолчанию .ГЭС-файл. Это означает, что код по умолчанию contructor / destructor компилируется вместе с двоичным файлом исполняемого файла хоста, а не вместе с двоичными файлами вашей библиотеки. Однако эти определения не могут действительно построить разделяемые классы. Поэтому, когда компоновщик входит в двоичный файл вашей библиотеки и пытается получить конструктор/деструктор, он не находит их, и вы получаете ошибку. Если код конструктора / деструктора был в вашем.cpp тогда ваша двоичная библиотека имеет то, что доступно для связывания.

Итак, это не имеет ничего общего с использованием unique_ptr вместо shared_ptr для вышеуказанного сценария, пока вы используете современные компиляторы (старый компилятор VC++ может иметь ошибку в реализации unique_ptr, но VC++ 2015 отлично работает на моей машине).

таким образом, мораль истории заключается в том, что ваш заголовок должен оставаться свободным от любого определения конструктора/деструктора. Он может содержать только их объявление. Например, ~MyClass()=default; в ГЭС не будет работать. Если вы разрешите компилятору вставить конструктор или деструктор по умолчанию, вы получите ошибку компоновщика.

один другая сторона Примечание: Если вы все еще получаете эту ошибку даже после того, как у вас есть конструктор и деструктор в файле cpp, то, скорее всего, причина в том, что ваша библиотека не компилируется должным образом. Например, однажды я просто изменил тип проекта с консоли на библиотеку в VC++, и я получил эту ошибку, потому что VC++ не добавил символ препроцессора _LIB, и это вызвало точно такое же сообщение об ошибке.

полное определение вещи требуется в точке создания экземпляра шаблона. Это точная причина, по которой компилируется идиома pimpl.

Если бы это было невозможно, люди не задавали бы такие вопросы, как этой.

просто для полноты:

заголовок: A. h

class B; // forward declaration

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

Источник A.cpp:

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

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

определение класса B должно быть видно конструктором, деструктором и всем, что может неявно удалить B. (Хотя конструктор не отображается в списке выше, в VS2017 даже конструктор нуждается в определении B. И это имеет смысл, учитывая, что в случае исключения в конструкторе unique_ptr снова уничтожается.)

Что касается меня,

QList<QSharedPointer<ControllerBase>> controllers;

просто включите заголовок ...

#include <QSharedPointer>