考虑用来解释一下这个典型的例子并不与前向声明做:
//in Handle.h file
class Body;
class Handle
{
public:
Handle();
~Handle() {delete impl_;}
//....
private:
Body *impl_;
};
//---------------------------------------
//in Handle.cpp file
#include "Handle.h"
class Body
{
//Non-trivial destructor here
public:
~Body () {//Do a lot of things...}
};
Handle::Handle () : impl_(new Body) {}
//---------------------------------------
//in Handle_user.cpp client code:
#include "Handle.h"
//... in some function...
{
Handle handleObj;
//Do smtg with handleObj...
//handleObj now reaches end-of-life, and BUM: Undefined behaviour
}
Run Code Online (Sandbox Code Playgroud)
我从标准中了解到这个案例正朝向UB,因为Body的析构函数是非常重要的.我想要了解的是这个的根本原因.
我的意思是,问题似乎是由Handle的dtor内联的事实"触发",因此编译器执行类似下面的"内联扩展"(这里几乎是伪代码).
inline Handle::~Handle()
{ …Run Code Online (Sandbox Code Playgroud) c++ destructor memory-management forward-declaration delete-operator
我写了这篇文章并得到了一些让我困惑的评论.
它基本上归结为我已经看到T2仅用作模板参数并错误地跳到结论我因此可以利用前向声明的机会:
struct T2;
struct T1
{
std::auto_ptr<T2> obj;
};
Run Code Online (Sandbox Code Playgroud)
如果我不继续在T2同一个TU中定义某个地方,则调用UB ,因为std::auto_ptr<T2>调用delete其内部T2*,并调用delete指向不完整类型的对象的指针,其完整类型具有非平凡的析构函数是未定义的:
[C++11: 5.3.5/5]:如果被删除的对象在删除时具有不完整的类类型,并且完整的类具有非平凡的析构函数或释放函数,则行为是未定义的.
我碰巧使用的GCC工具链 - v4.3.3(Sourcery G ++ Lite 2009q1-203) - 非常友好地通过一个注释让我知道:
注意:即使在定义类时声明析构函数也不会调用析构函数或特定于类的运算符delete.
虽然在其他GCC版本中似乎很难得到这种诊断.
我的抱怨是,如果delete指向不完整类型的实例的指针是不正确的而不是UB,那么发现这样的bug会容易得多,但这似乎是一个实现难以解决的问题,所以我明白为什么是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: …
这里有一个很好的小技巧,允许使用std::unique_ptr不完整的类型。
这是相关代码:
// File: erasedptr.h
#include <memory>
#include <functional>
// type erased deletor (an implementation type using "veneer")
template <typename T>
struct ErasedDeleter : std::function<void(T*)>
{
ErasedDeleter()
: std::function<void(T*)>( [](T * p) {delete p;} )
{}
};
// A unique_ptr typedef
template <typename T>
using ErasedPtr = std::unique_ptr<T, ErasedDeleter<T>>;
// Declare stuff with an incomplete type
struct Foo;
ErasedPtr<Foo> makeFoo();
// File: main.cpp (Foo's definition is not available in this translation unit)
#include "erasedptr.h"
int main() { …Run Code Online (Sandbox Code Playgroud)