c++ make_shared - Является ли std::unique_ptr<T>необходимым для определения полного определения T?




shared_ptr make_unique (7)

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

#include <memory>

class Thing;

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

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

1> C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067): ошибка C2027: использование неопределенного типа 'Thing'

Замените std::unique_ptr на std::shared_ptr и он скомпилируется.

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

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


Answers

Как по мне,

QList<QSharedPointer<ControllerBase>> controllers;

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

#include <QSharedPointer>

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

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


Принято here .

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

Неопределенное поведение может возникать, когда у вас есть неполный тип, и вы вызываете 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's .


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


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

Заголовок: Ах

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 снова уничтожается.)


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

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

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

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

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

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


Большинство предыдущих ответов предполагают, что ваш загрузчик маленький и простой, как правило, так; однако, если он становится более сложным (т. е. вам нужно иметь возможность загружаться с порта Ethernet, USB-порта или последовательного порта ... вам необходимо проверить код, который загружается, прежде чем стирать существующий код, и т.д.) вы можете рассмотреть C ++.

Я также обнаружил, что загрузчик и приложение, как правило, совместно используют некоторое количество общего кода, поэтому вы также можете рассмотреть возможность использования того же языка, что и ваше приложение, для облегчения совместного использования кода.





c++ visual-studio-2010 c++11 stl unique-ptr