与auto_ptr声明不同,unique_ptr声明在其模板类型是不完整类型时是否定义良好?

Lig*_*ica 12 c++ auto-ptr unique-ptr language-lawyer c++11

我写了这篇文章并得到了一些让我困惑的评论.

它基本上归结为我已经看到T2仅用作模板参数并错误地跳到结论我因此可以利用前向声明的机会:

struct T2;

struct T1
{
    std::auto_ptr<T2> obj;
};
Run Code Online (Sandbox Code Playgroud)

如果我不继续在T2同一个TU中定义某个地方,则调用UB ,因为std::auto_ptr<T2>调用delete其内部T2*,并调用delete指向不完整类型的对象的指针,其完整类型具有非平凡的析构函数是未定义的:

[C++11: 5.3.5/5]: 如果被删除的对象在删除时具有不完整的类类型,并且完整的类具有非平凡的析构函数或释放函数,则行为是未定义的.

我碰巧使用的GCC工具链 - v4.3.3(Sourcery G ++ Lite 2009q1-203) - 非常友好地通过一个注释让我知道:

注意:即使在定义类时声明析构函数也不会调用析构函数或特定于类的运算符delete.

虽然在其他GCC版本中似乎很难得到这种诊断.

我的抱怨是,如果delete指向不完整类型的实例的指针是不正确的而不是UB,那么发现这样的bug会容易得多,但这似乎是一个实现难以解决的问题,所以我明白为什么是UB.

但后来我被告知,如果我使用它std::unique_ptr<T2>,这将是安全和合规的.

据称n3035在20.9.10.2说:

模板参数Tunique_ptr可能是一个不完整的类型.

我在C++ 11中找到的所有内容都是:

[C++11: 20.7.1.1.1]:

/ 1类模板default_delete用作类模板的默认删除器(销毁策略)unique_ptr.

/ 2模板参数Tdefault_delete可能是一个不完整的类型.

但是,default_deleteoperator()的确需要一个完整的类型:

[C++11: 20.7.1.1.2/4]:如果T是不完整类型,则程序格式错误.


我想我的问题是:

我的文章中的评论者是否正确地说,仅由以下代码组成的翻译单元格式正确且定义明确?或者他们错了吗?

struct T2;

struct T1
{
    std::unique_ptr<T2> obj;
};
Run Code Online (Sandbox Code Playgroud)

如果它们是正确的,那么编译器应该如何实现这一点,因为它有充分的理由使它成为UB,至少在std::auto_ptr使用它时?

And*_*ard 9

根据香草萨特在GOTW#100,unique_ptr由相同的问题的困扰为auto_ptr相对于不完整的类型.

...虽然unique_ptr和shared_ptr都可以用不完整的类型实例化,但unique_ptr的析构函数需要一个完整的类型才能调用delete ...

他的建议是T1在头文件中声明包含类(即)的析构函数,然后将它的定义放在T2一个完整类型的翻译单元中.

// T1.h
struct T2;

struct T1
{
  ~T1();
  std::unique_ptr< T2 >;
};

// T1.cpp
#include "T2.h"

T1::~T1()
{
}
Run Code Online (Sandbox Code Playgroud)


How*_*ant 8

下面的例子是为了阐明之间的差的尝试std::auto_ptr<T>std::unique_ptr<T>.首先考虑这个程序包含2个源文件和1个头文件:

标题:

// test.h

#ifndef TEST_H
#define TEST_H

#include <memory>

template <class T>
using smart_ptr = std::auto_ptr<T>;

struct T2;

struct T1
{
    smart_ptr<T2> obj;

    T1(T2* p);
};

T2*
source();

#endif  // TEST_H
Run Code Online (Sandbox Code Playgroud)

第一来源:

// test.cpp

#include "test.h"

int main()
{
    T1 t1(source());
}
Run Code Online (Sandbox Code Playgroud)

第二个来源:

// test2.cpp

#include "test.h"
#include <iostream>


struct T2
{
    ~T2() {std::cout << "~T2()\n";}
};

T1::T1(T2* p)
    : obj(p)
{
}

T2*
source()
{
    return new T2;
}
Run Code Online (Sandbox Code Playgroud)

这个程序应该编译(它可以编译一个警告,但它应该编译).但在运行时它会演示未定义的行为.它可能不会输出:

~T2()
Run Code Online (Sandbox Code Playgroud)

这表明T2析构函数尚未运行.至少它不在我的系统上.

如果我将test.h更改为:

template <class T>
using smart_ptr = std::unique_ptr<T>;
Run Code Online (Sandbox Code Playgroud)

然后编译器需要输出诊断(错误).

也就是说,当你犯这个错误时,auto_ptr你会遇到运行时错误.当你犯这个错误时,unique_ptr你会得到一个编译时错误.而之间的差别auto_ptrunique_ptr.

要修复编译时错误,必须~T1()T2完成后进行概述.在test2.cpp之后添加T2:

T1::~T1() = default;
Run Code Online (Sandbox Code Playgroud)

现在它应该编译输出:

~T2()
Run Code Online (Sandbox Code Playgroud)

您可能还想声明并概述移动成员:

T1::T1(T1&&) = default;
T1& T1::operator=(T1&&) = default;
Run Code Online (Sandbox Code Playgroud)

您可以使用相同的修复程序,auto_ptr它将再次正确.但同样,和之间的区别在于auto_ptr,unique_ptr在运行时之前,您没有发现需要进行一些调试(编译器可能提供的模数可选警告).使用后者,您可以保证在编译时找到答案.