Pimpl - Почему make_unique может быть вызван для неполного типа - PullRequest
0 голосов
/ 05 сентября 2018

Почему вызов 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 не является проблемой?

Ответы [ 2 ]

0 голосов
/ 05 сентября 2018

Причина, по которой этот компилятор находится здесь, в [temp.point] ¶8 :

Специализация для шаблона функции, шаблон функции-члена, или функция-член или статический член данных шаблона класса может иметь несколько точек реализации в пределах единицы перевода, и в дополнение к описанным выше точкам реализации, для любого такая специализация, которая имеет точку конкретизации в рамках блок перевода, конец блока перевода также считается точка создания экземпляра. Специализация для шаблона класса имеет в самое большее один момент создания экземпляра в единице перевода [...] Если две разные точки создания дают специализации шаблона разные значения согласно правилу единого определения, программа плохо сформирована, нет диагностики требуется.

Обратите внимание на окончание этой цитаты, поскольку мы дойдем до нее в редактировании ниже, но сейчас то, что происходит на практике по фрагменту OP, - это то, что компилятор использует дополнительно рассматриваемый момент создания экземпляра make_unique(), который помещается в конце блока перевода, чтобы в нем были определения, отсутствующие в исходной точке использования в коде. Это разрешено делать в соответствии с этим пунктом из спецификации.

Обратите внимание, это больше не компилируется:

struct Foo; int main(){ std::make_unique<Foo>(); } struct Foo { ~Foo() = delete; };

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


Редактировать: Наконец, кажется, что, хотя у вас есть эти несколько точек создания экземпляров, это не означает, что поведение определяется, если определение отличается между этими точками. Обратите внимание на последнее предложение в приведенной выше цитате, согласно которому эта разница определяется правилом One Definition . Это взято прямо из моего комментария к ответу @hvd, который обнародовал это здесь: См. здесь в Правиле Единого Определения :

Каждая программа должна содержать ровно одно определение каждого не встроенного функция или переменная, которая используется в этой программе за пределами отклоненное заявление; Диагностика не требуется. Определение может появиться явно в программе, его можно найти в стандарте или пользовательская библиотека или ...

И поэтому в случае ОП очевидно, что есть разница между двумя точками реализации, поскольку, как отметил сам @hvd, первый имеет неполный тип, а второй - нет. На самом деле, это различие составляет два разных определения, и поэтому очень мало сомнений в том, что эта программа плохо сформирована.

0 голосов
/ 05 сентября 2018

make_unique имеет несколько точек создания: конец единицы перевода также является точкой создания. То, что вы видите, это то, что компилятор создает экземпляр make_unique только после завершения CacheImpl / F. Компиляторы могут это делать. Ваш код некорректен, если вы полагаетесь на него, и компиляторы не обязаны обнаруживать ошибку.

14.6.4.1 Точка создания [временная точка]

8 Специализация для шаблона функции, шаблона функции-члена или функции-члена или статического члена данных шаблона класса может иметь несколько точек создания экземпляров в единице перевода и в дополнение к описанным выше точкам создания экземпляров. для любой такой специализации, которая имеет точку создания экземпляра в единице перевода, конец единицы перевода также считается точкой создания. [...] Если две разные точки инстанцирования придают специализации шаблона разные значения в соответствии с одним правилом определения (3.2), программа некорректна, диагностика не требуется.

...