为什么构造std :: unique_ptr用于不完整类型编译?

时间:2017-12-21 15:49:27

标签: c++ c++11 templates c++14 language-lawyer

代码:

#include <memory>

struct Data;
std::unique_ptr<Data> make_me();

int main()
{
    std::unique_ptr<Data> m = make_me();
    return 0;
}

当然失败了:

In file included from <source>:1:
In file included from /opt/compiler-explorer/gcc-7.1.0/lib/gcc/x86_64-linux-gnu/7.1.0/../../../../include/c++/7.1.0/memory:80:
/opt/compiler-explorer/gcc-7.1.0/include/c++/7.1.0/bits/unique_ptr.h:76:16: error: invalid application of 'sizeof' to an incomplete type 'Data'
        static_assert(sizeof(_Tp)>0,
                      ^~~~~~~~~~~
/opt/compiler-explorer/gcc-7.1.0/include/c++/7.1.0/bits/unique_ptr.h:268:4: note: in instantiation of member function 'std::default_delete<Data>::operator()' requested here
          get_deleter()(__ptr);
          ^
8 : <source>:8:31: note: in instantiation of member function 'std::unique_ptr<Data, std::default_delete<Data> >::~unique_ptr' requested here
    std::unique_ptr<Data> m = make_me();
                              ^
3 : <source>:3:8: note: forward declaration of 'Data'
struct Data;
       ^
1 error generated.
Compiler returned: 1

但是在上面的代码末尾添加以下行可以很好地编译:

struct Data {};

我的问题是为什么这个代码在std :: unique_ptr实例化之后声明Data时编译并运行?看起来,这两种情况都应该以相同/类似的错误失败。

关于godbolt的完整示例:https://godbolt.org/g/FQqxwN

2 个答案:

答案 0 :(得分:11)

这是有效的,因为template的{​​{3}}位于Data的定义之后。从标准:

  

point of instantiation

     

对于类模板特化,类成员模板特化或类模板的类成员的特化,如果特殊化是隐式实例化的,因为它是从另一个模板特化中引用的,如果引用特化的上下文取决于模板参数,并且如果在封闭模板的实例化之前未实例化特化,则实例化的点紧接在封闭模板的实例化之前。否则,这种特化的实例化点紧接在命名空间范围声明或引用特化的定义之前。

     

函数模板,成员函数模板或类模板的成员函数或静态数据成员的特化可以在翻译单元中具有多个实例化点,并且除了实例化点之外如上所述,对于在翻译单元中具有实例化点的任何此类专业化,翻译单元的末尾也被视为实例化点。类模板的特化在其中最多只有一个实例化点。翻译单位。任何模板的特化可以在多个翻译单元中具有实例化点。 如果两个不同的实例化点根据单一定义规则赋予模板特化不同的含义,则程序格式错误,无需诊断。

请注意,由于报价中的最后一句,这可能是 格式错误(NDR)。我没有足够的信心判断这是否确实是不正确的。

答案 1 :(得分:5)

如果您仔细阅读,问题在于所包含的Data对象的删除。在

get_deleter()(__ptr)

部分是大提示。

这里发生的是唯一指针对象m超出main函数末尾的范围,因此需要删除指向的数据。但是,由于没有析构函数,默认删除器无法处理它。

要解决此问题,您可以添加结构的定义,这将定义它,默认删除器将能够知道类型。 你可以为指针添加一个新的删除器,其中一个(在这种情况下)可能无效:

auto null_deleter = [](Data*){ /* Do nothing */ };
...
std::unique_ptr<Data, decltype(null_deleter)> m = make_me();

当然,如果你想真正删除数据,那么要么定义结构,要么修改删除器使它delete指针(这使得需要完整的结构定义,但是删除器可以在另一个tranaslation-unit中定义,可能与定义make_me时相同。