Pimpl-为什么可以在不完整的类型上调用make_unique [英] Pimpl - Why can make_unique be called on an incomplete type

查看:96
本文介绍了Pimpl-为什么可以在不完整的类型上调用make_unique的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么make_unique调用会编译? make_unqiue不需要其模板参数为完整类型吗?

Why does the make_unique call compile? Doesn't make_unqiue require its template argument to be a complete type ?

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

struct F {};


问题来自我的问题" 与我的 PIMPL实现:


The question orignated from my "problem" with my PIMPL implementation:

我确实理解为什么必须在实现类(PIMPL)的cpp文件中对析构函数进行用户声明和定义.

I do understand why the destructor has to be user declared and defined inside the cpp file for the Implementation class (PIMPL).

但是移动包含pimpl-的类的构造函数仍会编译.

But moving the constructor of the class containing the pimpl- still compiles.

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

Could someone explain why this compiles? Why is there a difference between construction and destruction? If the instantiation of the destructor and the instantiation of the default_deleter is a problem why is the instantiation of make_unique not a problem ?

推荐答案

之所以在 [温度点]¶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()放在翻译单元的末尾,因此它将具有在代码的原始使用点缺少的定义.可以根据规范中的此条款进行操作.

Do notice the ending of this quote, as we will get to it in the edit below, but for now what happens in practice per the OP's snippet is the compiler uses the additionally considered instantiation point of make_unique() that is placed at the end of the translation unit, so that it will have definitions that are missing at the original point of usage in the code. It is allowed to do so according to this clause from the spec.

请注意,此文件不再编译:

Note this no longer compiles:

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

在这种情况下,编译器不会在实例化点上遗漏,它只是根据用于生成模板代码的翻译单元中的哪一点来推迟编译.

As in, the compiler doesn't miss on the point of instantiation, it only defers it in terms of which point in the translation unit it uses to generate code for the template.

最后看来,即使您具有这些多个实例化点,也并不意味着如果这些点之间的定义不同,就定义了行为.请注意上面引用中的最后一句话,根据该定义,差异一个定义规则定义.这是从我的评论直接到@hvd的答案的,他在这里揭露了这一点: 请参阅此处在一个定义规则"中:

Finally it seems that even though you have these multiple instantiation points, it doesn't mean that the behavior is defined if the definition is different between these points. Note the last sentence in the above quote, according to which this difference is defined by the One Definition Rule. This is taken straight from my comment to the answer by @hvd, who brought this to light here: See 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本人指出的那样,第一个实例是不完整的类型,而第二个实例则不是.确实,这种差异构成了两个不同的定义,因此,该程序的格式不正确是毫无疑问的.

And so in the OP's case, the're is obviously a difference between the two instantiation points, in that, as @hvd himself noted, the first one is of an incomplete type, and the second one isn't. Indeed, this difference constitutes two different definitions and so there's very little doubt this program is ill-formed.

这篇关于Pimpl-为什么可以在不完整的类型上调用make_unique的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆