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

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

#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Реализация, которая требует полного определения и полностью зависит от реализации.

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

7 ответов

Решение

Принято отсюда.

Большинство шаблонов в стандартной библиотеке 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, Более подробную информацию по этому вопросу можно найти в Lightness Races in Orbit здесь.

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

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

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

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

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

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

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

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

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

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

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

Я искал способ использовать идиому PIMPL с std::unique_ptr. Это руководство - отличный ресурс.

Вкратце, вот что вы можете сделать, чтобы это работало:

my_class.h

#include <memory>

class Thing;

class MyClass
{
    ~MyClass(); // <--- Added
    std::unique_ptr< Thing > my_thing;
};

my_class.cpp

MyClass::~MyClass() = default; // Or a custom implementation

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

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

Как по мне,

QList<QSharedPointer<ControllerBase>> controllers;

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

#include <QSharedPointer>

Простой ответ - просто используйте вместо этого shared_ptr.

Другие вопросы по тегам