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

Mic*_*Łoś 15 c++ templates language-lawyer c++11 c++14

码:

#include <memory>

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

int main()
{
    std::unique_ptr<Data> m = make_me();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当然失败了:

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
Run Code Online (Sandbox Code Playgroud)

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

struct Data {};
Run Code Online (Sandbox Code Playgroud)

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

关于godbolt的整个例子:https://godbolt.org/g/FQqxwN

Vit*_*meo 11

这是有效的,因为a 的实例化点template位于定义之后Data.从标准:

[temp.point]

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

函数模板,成员函数模板或类模板的成员函数或静态数据成员的特化可以在翻译单元内具有多个实例化点,并且除了上述实例化的点之外,对于任何这样的实例化.在翻译单元内具有实例化点的专门化,翻译单元的末尾也被认为是实例化的点.类模板的专门化在翻译单元中最多只有一个实例化点.任何模板的特化可以在多个翻译单元中具有实例化点.如果两个不同的实例化点根据单定义规则给出模板特化的不同含义,则程序形成错误,不需要诊断.

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

  • 结论:我很确定这是不正确的NDR,因为实例化会有所不同,而且只是因为编译器实现策略才会编译.请记住,"做用户想要的"是UB的有效结果("格式错误,NDR"实际上只是"行为未定义"的别名). (3认同)
  • 因此,给出引号中的最后一句,并不意味着std :: unique_ptr的构造函数有两个实例化点(一个在main()中,第二个在转换单元的末尾),它们的区别在于"含义" ,这意味着该计划是不正确的? (2认同)
  • 是的,这是形成不良的NDR. (2认同)

Som*_*ude 5

如果你仔细阅读,问题是删除包含的Data对象.该

get_deleter()(__ptr)
Run Code Online (Sandbox Code Playgroud)

部分是大提示.

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

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

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

当然,如果你想要实际删除数据,那么要么定义结构,要么修改删除器使它delete成为指针(这使得它无论如何都需要完整的结构定义,但是删除器可以在另一个tranaslation-unit中定义,可能同样在哪里make_me定义).

  • 虽然你的评论是有价值的,但这不是我的问题的答案:我问为什么这个代码**在最后添加结构定义后编译**,而不是**如何使它编译**:). (2认同)