与auto_ptr声明不同,当unique_ptr声明的模板类型不完整时,它是否是明确定义的?
Is it true that a unique_ptr declaration, unlike a auto_ptr declaration, is well-defined when its template type is of an incomplete type?
我写了这篇文章,得到了一些让我感到困惑的评论。
它基本上归结为我看到T2
仅用作模板参数,并错误地得出结论,因此我可以借此机会进行前瞻性声明:
struct T2;
struct T1
{
std::auto_ptr<T2> obj;
};
如果我不继续在同一 TU 中的某处定义T2
,这将调用 UB,因为std::auto_ptr<T2>
在其内部T2*
上调用delete
,并且在指向不完整类型的对象的指针上调用delete
,该对象的完整类型具有非平凡析构函数是未定义的:
[C++11: 5.3.5/5]:
如果要删除的对象在删除时具有不完整的类类型,并且完整类具有非平凡析构函数或释放函数,则行为是未定义的。
我碰巧使用的 GCC 工具链 - v4.3.3(Sourcery G++ Lite 2009q1-203) - 很好心地让我知道:
注意:不会调用析构函数和特定于类的运算符 delete,即使在定义类时声明了它们也是如此。
尽管在其他 GCC 版本中似乎很难获得此诊断。
我的抱怨是,如果delete
指向不完整类型实例的指针格式不正确而不是 UB,那么发现这样的错误会容易得多,但这似乎是实现解决的一个棘手问题,所以我理解为什么它是 UB。
但后来我被告知,如果我改用std::unique_ptr<T2>
,这将是安全和合规的。
据称,N3035 在 20.9.10.2 中说:
模板参数
T
unique_ptr
可能是不完整的类型。
我在 C++11 中所能找到的只是:
[C++11: 20.7.1.1.1]:
/1类模板
default_delete
用作类模板unique_ptr
的默认删除程序(销毁策略)。/2模板参数
T
的default_delete
可能是不完整的类型。
但是,default_delete
的operator()
确实需要一个完整的类型:
[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 的析构函数需要完整类型 以调用删除...
他的建议是声明包含类的析构函数(即T1
),然后将其定义放在翻译单元中,其中T2
是完整类型。
// T1.h
struct T2;
struct T1
{
~T1();
std::unique_ptr< T2 >;
};
// T1.cpp
#include "T2.h"
T1::~T1()
{
}
下面的示例尝试演示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
之间的区别。
若要修复编译时错误,必须在T2
完成后概述~T1()
。 在 test2 中.cpp在T2
后添加:
T1::~T1() = default;
现在它应该编译并输出:
~T2()
您可能还需要声明和分级显示移动成员:
T1::T1(T1&&) = default;
T1& T1::operator=(T1&&) = default;
您可以使用auto_ptr
进行相同的修复,并且再次是正确的。 但同样,auto_ptr
和unique_ptr
之间的区别在于,对于前者,您直到运行时才会发现您有一些调试要做(编译器可能会给出的模可选警告)。 使用后者,您可以保证在编译时找到答案。
- .cpp和.h文件中的模板专用化声明
- 未在作用域中声明unordered_map
- C++避免重复声明的语法是什么
- 如何确保C++函数在定义之前声明(如override关键字)
- 错误:未在此范围内声明'reverse'
- 奇怪的(对我来说)返回声明 - 在谷歌上找不到任何关于它的信息
- 为什么在定义函数之前先声明它
- 如何声明特征矩阵,然后通过嵌套循环初始化它
- #ifdef和未声明的标识符
- 没有显式声明的int[]中的foreach
- 在基于范围的for循环中使用结构化绑定声明
- 在将变量声明为引用时,堆在释放后使用
- C++:无法访问声明的受保护成员
- OpenCV - Ptr 语法和类定义/声明 - 混淆?
- 这个声明"int(*ptr[3])();"是什么意思?
- 非ptr新声明符中的错误表达式
- Boost Scoped Ptr-声明与分配
- 如何转发声明boost智能ptr
- 在声明char const *ptr ="某些字符"时是否有必要使用"const"?
- 共享Ptr与普通Ptr:声明后的对象创建