Pimpl - Почему make_unique можно вызвать на неполном типе?
Почему компиляция make_unique компилируется? Не make_unqiue требует, чтобы его аргумент шаблона был полным типом?
struct F;
int main()
{
std::make_unique<F>();
}
struct F {};
Вопрос, который был задан из моей "проблемы" с моей реализацией PIMPL:
Я понимаю, почему деструктор должен быть объявлен и определен пользователем внутри файла cpp для класса реализации (PIMPL).
Но перемещение конструктора класса, содержащего pimpl-, еще компилируется.
class Object
{};
class CachedObjectFactory
{
public:
CachedObjectFactory();
~CachedObjectFactory();
std::shared_ptr<Object> create(int id) const;
private:
struct CacheImpl;
std::unique_ptr<CacheImpl> pImpl;
};
Теперь файл cpp:
// constructor with make_unique on incompete type ?
CachedObjectFactory::CachedObjectFactory()
: pImpl(std::make_unique<CacheImpl>())
{}
struct CachedObjectFactory::CacheImpl
{
std::map<int, std::shared_ptr<Object>> idToObjects;
};
//deferred destructor
CachedObjectFactory::~CachedObjectFactory() = default;
Может кто-нибудь объяснить, почему это компилируется? Почему существует разница между строительством и разрушением? Если создание деструктора и создание экземпляра default_deleter является проблемой, почему создание make_unique не является проблемой?
Ответы
Ответ 1
make_unique
имеет несколько точек инстанцирования: конец единицы перевода также является точкой инстанцирования. То, что вы видите, это компилятор, make_unique
экземпляр make_unique
после CacheImpl
/F
Составителям разрешено это делать. Ваш код плохо сформирован, если вы полагаетесь на него, и компиляторы не обязаны обнаруживать ошибку.
14.6.4.1 Точка инстанцирования [temp.point]
8 Специализация шаблона функции, шаблона функции-члена или функции-члена или статического элемента данных шаблона класса может иметь несколько точек инстанцирования внутри единицы перевода и в дополнение к точкам инстанцирования, описанным выше, для любого такая специализация, которая имеет точку инстанцирования внутри единицы перевода, конец единицы перевода также считается точкой инстанцирования. [...] Если две разные точки инстанцирования дают шаблонную специализацию разных значений в соответствии с одним правилом определения (3.2), программа плохо сформирована, не требуется диагностика.
Ответ 2
Причина, по которой этот компилятор находится здесь, в [temp.point] ¶8:
Специализация шаблона функции, шаблона функции-члена или функции-члена или статического элемента данных шаблона класса может иметь несколько точек инстанцирования внутри единицы перевода и в дополнение к точкам создания, описанным выше, для любого такого специализация, которая имеет точку инстанцирования в пределах единицы перевода, конец единицы перевода также считается точкой инстанцирования. Специализация для шаблона класса имеет не более одной точки инстанцирования внутри единицы перевода [...] Если две разные точки инстанцирования предоставляют специалисту шаблона разные значения в соответствии с правилом определения одного из вариантов, программа плохо сформирована, нет требуется диагностика.
Обратите внимание на окончание этой цитаты, так как мы перейдем к ней в приведенном ниже редактировании, но на данный момент, что происходит на практике по фрагменту OP, компилятор использует дополнительно рассмотренную точку создания make_unique()
которая помещается в конец блок перевода, так что он будет иметь определения, которые отсутствуют в исходной точке использования в коде. Это разрешено в соответствии с этим пунктом из спецификации.
Обратите внимание, что это больше не компилируется:
struct Foo; int main(){ std::make_unique<Foo>(); } struct Foo { ~Foo() = delete; };
Как и в случае, компилятор не пропустит точку инстанцирования, он только отбрасывает его с точки зрения того, в какой точке блока перевода он использует для генерации кода для шаблона.
Изменение: Наконец, кажется, что даже если у вас есть несколько точек создания, это не означает, что поведение определяется, если определение отличается между этими точками. Обратите внимание на последнее предложение в приведенной выше цитате, согласно которой это различие определено Правилом одного определения. Это взято прямо из моего комментария к ответу @hvd, который осведомил это здесь: см. Здесь в правиле одного определения:
Каждая программа должна содержать ровно одно определение каждой не-встроенной функции или переменной, которая не используется в этой программе вне отбрасываемого оператора; не требуется диагностика. Определение может явно отображаться в программе, оно может быть найдено в стандартной или определяемой пользователем библиотеке или...
И так, в случае OP, это, очевидно, разница между двумя точками создания, в том, что, как отметил сам @hvd, первый из них имеет неполный тип, а второй - нет. Действительно, эта разница составляет два разных определения, и поэтому очень мало сомнений в том, что эта программа плохо сформирована.