我写了this article并得到了一些让我困惑的评论。
它基本上归结为我已经看到T2
仅用作模板参数而错误地跳到了我可以借此机会进行前瞻宣言的结论:
struct T2;
struct T1
{
std::auto_ptr<T2> obj;
};
如果我不继续在同一个TU中的某个位置定义T2
,则会调用UB,因为std::auto_ptr<T2>
在其内部delete
上调用T2*
,并且{{ 3}}:
[C++11: 5.3.5/5]:
如果被删除的对象在删除时具有不完整的类类型,并且完整的类具有非平凡的析构函数或释放函数,则行为是未定义的。
我碰巧使用的GCC工具链 - v4.3.3(Sourcery G ++ Lite 2009q1-203) - 非常友好地告诉我一个注意事项:
注意:即使它们是在定义类时声明的,也不会调用析构函数或类特定的运算符delete。
虽然在其他GCC版本中似乎很难得到这种诊断。
我的抱怨是,如果delete
指向不完整类型的实例的指针格式错误而不是UB,那么发现这样的错误要容易得多,但这似乎是解决实现的难以解决的问题,所以我理解为什么它是UB。
但后来我被告知,如果我使用std::unique_ptr<T2>
代替,这将是安全且合规的。
T
的模板参数unique_ptr
可能是不完整的类型。
我在C ++ 11中找到的所有内容都是:
[C++11: 20.7.1.1.1]:
/ 1 类模板
default_delete
充当类模板unique_ptr
的默认删除器(销毁策略)。/ 2
T
的模板参数default_delete
可能是不完整的类型。
但是,default_delete
的{{1}}确实需要完整的类型:
operator()
如果[C++11: 20.7.1.1.2/4]:
是不完整的类型,则该程序格式错误。
我想我的问题是:
我的文章中的评论者是否正确地说只包含以下代码的翻译单元格式正确并且定义明确?或者他们错了?
T
如果它们是正确的,那么编译器应该如何实现这一点,因为它有充分的理由使它成为UB,至少在使用struct T2;
struct T1
{
std::unique_ptr<T2> obj;
};
的时候?
答案 0 :(得分:9)
根据GOTW #100中的Herb Sutter,unique_ptr
在不完整类型方面遇到与auto_ptr
相同的问题。
...尽管unique_ptr和shared_ptr都可以用。实例化 不完整的类型,unique_ptr的析构函数需要一个完整的类型 为了调用删除...
他的建议是在头文件中声明包含类的析构函数(即T1
),然后将其定义放在T2
为完整类型的翻译单元中。
// T1.h
struct T2;
struct T1
{
~T1();
std::unique_ptr< T2 >;
};
// T1.cpp
#include "T2.h"
T1::~T1()
{
}
答案 1 :(得分: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
之间的区别在于,对于前者,您在运行时才发现需要进行一些调试(编译器可能提供模数可选警告)。对于后者,您可以保证在编译时找到它。