Требуется ли std :: unique_ptr <T>, чтобы знать полное определение T? - PullRequest
228 голосов
/ 16 мая 2011

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

#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, не так ли?

Ответы [ 8 ]

304 голосов
/ 22 мая 2011

Принято с здесь .

Для большинства шаблонов в стандартной библиотеке 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 здесь .

41 голосов
/ 16 мая 2011

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

13 голосов
/ 22 мая 2011

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

5 голосов
/ 10 февраля 2017

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

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

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

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

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

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

2 голосов
/ 12 января 2018

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

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

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

1 голос
/ 22 мая 2011

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

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

0 голосов
/ 07 июня 2019

Простой ответ - использовать shared_ptr.

0 голосов
/ 22 августа 2018

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

QList<QSharedPointer<ControllerBase>> controllers;

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

#include <QSharedPointer>
...