Pimpl - 为什么可以在不完整的类型上调用 make_unique

Pimpl - Why can make_unique be called on an incomplete type

为什么 make_unique 调用可以编译? make_unqiue 不要求它的模板参数是一个完整的类型吗?

struct F;
int main()
{
  std::make_unique<F>();
}

struct F {};

问题源自我的PIMPL 实现的“问题”:

我理解为什么析构函数必须由用户在 cpp 文件中声明和定义以实现 class (PIMPL)。

但是移动包含 pimpl- 的 class 的构造函数仍然可以编译。

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 incomplete 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 的实例化不是问题?

make_unique有多个实例化点:翻译单元的结尾也是一个实例化点。您看到的是编译器仅在 CacheImpl/F 完成后实例化 make_unique。允许编译器这样做。如果你依赖它,你的代码是错误的,编译器不需要检测错误。

14.6.4.1 Point of instantiation [temp.point]

8 A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. [...] If two different points of instantiation give a template specialization different meanings according to the one definition rule (3.2), the program is ill-formed, no diagnostic required.

编译的原因在[temp.point]¶8:

A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit [...] If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.

请注意这句话的结尾,因为我们将在下面的编辑中看到它,但现在根据 OP 的片段在实践中发生的是编译器使用 另外考虑的 位于翻译单元末尾的 make_unique() 的实例化点,因此它将具有在代码中的原始使用点缺少的定义。根据规范中的此条款,允许这样做。

注意这不再编译:

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

例如,编译器不会遗漏实例化点,它只会根据它用于生成模板代码的翻译单元中的哪个点来推迟实例化。


编辑:最后看来,即使您有这些多个实例化点,如果这些点之间的定义不同,也不意味着定义了行为。请注意上面引文中的最后一句话,据此 差异 单一定义规则 定义。这是直接从我对@hvd 的回答的评论中得出的,他在这里揭示了这一点: 见 here in the One Definition Rule:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or ...

所以在 OP 的情况下,这显然是两个实例化点之间的区别,正如@hvd 自己指出的那样,第一个是不完整的类型,第二个不是.事实上,这种差异构成了两个不同的定义,因此毫无疑问这个程序是病式的。