12

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

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

struct T2;

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

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

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

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

注意:析构函数和特定于类的操作符 delete 都不会被调用,即使它们是在定义类时声明的。

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

我的抱怨是,如果指向不完整类型的实例的指针格式错误而不是 UB,那么发现这样的错误会容易得多delete,但这对于实现来说似乎是一个难以解决的问题,所以我明白为什么是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_delete'soperator()确实需要一个完整的类型:

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


我想我的问题是这样的:

我文章的评论者是否正确地说仅包含以下代码的翻译单元格式正确且定义明确?还是他们错了?

struct T2;

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

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

4

2 回答 2

9

根据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()
{
}
于 2012-10-07T00:14:22.147 回答
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

第一个来源:

// 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_ptr和之间的区别unique_ptr

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

T1::~T1() = default;

现在它应该编译并输出:

~T2()

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

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

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

于 2012-10-07T02:54:51.570 回答