提问者:小点点

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


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

这基本上可以归结为我看到T2仅用作模板参数,并错误地得出结论,我因此可以借此机会向前声明:

struct T2;

struct T1
{
    std::auto_ptr<T2> obj;
};

如果我不在同一个TU中定义T2,这将调用UB,因为std::auto\u ptr

[C 11:5.3.5/5]:如果要删除的对象在删除点具有不完整的类类型,并且完整的类具有非平凡的析构函数或解除分配函数,则行为未定义。

我碰巧使用的GCC工具链是-v4。3.3(Sourcery G Lite 2009q1-203)-非常感谢您给我一个提示:

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

尽管在其他GCC版本中似乎很难得到这种诊断。

我的抱怨是,如果deleteing指向不完整类型实例的指针的格式不正确,而不是UB,那么发现这样的错误会容易得多,但这似乎是一个难以解决的问题,需要实现来解决,所以我理解为什么它是UB。

但是我被告知,如果我使用std::unique\u ptr

据称n3035在20.9表示。10.2:

unique\u ptr的模板参数T可能是不完整的类型。

我能在C11找到的只有:

[C 11:20.7.1.1.1]:

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

/2default\u delete的模板参数T可能是不完整的类型。

但是,default\u delete操作符()确实需要完整的类型:

[C 11:20.7.1.1.2/4]:如果T是不完整的类型,则程序的格式不正确。

我想我的问题是:

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

struct T2;

struct T1
{
    std::unique_ptr<T2> obj;
};

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


共2个答案

匿名用户

根据GOTW#100中的Herb Sutter所述,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()
{
}

匿名用户

下面的示例试图演示std::auto_ptr

标题:

// 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

第一来源:

// test.cpp

#include "test.h"

int main()
{
    T1 t1(source());
}

第二来源:

// 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;
}

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

~T2()

这表示T2的析构函数尚未运行。至少在我的系统上没有。

如果我将test. h更改为:

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

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

也就是说,当您使用auto_ptr犯此错误时,您会得到一个运行时错误。当您使用unique\ptr犯此错误时,您会得到一个编译时错误。这就是auto_ptrunique_ptr之间的区别。

要修复编译时错误,必须在T2完成后概述~T1()。在测试2中。在T2之后添加cpp:

T1::~T1() = default;

现在,它应该编译并输出:

~T2()

您可能还需要声明和概述移动成员:

T1::T1(T1&&) = default;
T1& T1::operator=(T1&&) = default;

您可以使用auto_ptr进行相同的修复,并且它将再次正确。但同样,auto_ptrunique_ptr之间的区别在于,对于前者,直到运行时才发现需要进行一些调试(编译器可能会给出模可选警告)。对于后者,您可以保证在编译时找到答案。