我写了这篇文章,得到了一些让我困惑的评论。
这基本上可以归结为我看到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版本中似乎很难得到这种诊断。
我的抱怨是,如果delete
ing指向不完整类型实例的指针的格式不正确,而不是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
时是这样?
根据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_ptr
和unique_ptr
之间的区别。
要修复编译时错误,必须在T2
完成后概述~T1()
。在测试2中。在T2
之后添加cpp:
T1::~T1() = default;
现在,它应该编译并输出:
~T2()
您可能还需要声明和概述移动成员:
T1::T1(T1&&) = default;
T1& T1::operator=(T1&&) = default;
您可以使用auto_ptr
进行相同的修复,并且它将再次正确。但同样,auto_ptr
和unique_ptr
之间的区别在于,对于前者,直到运行时才发现需要进行一些调试(编译器可能会给出模可选警告)。对于后者,您可以保证在编译时找到答案。