Opaque Pointer的陷阱

来源:互联网 发布:2017淘宝网双11销售额 编辑:程序博客网 时间:2024/05/17 03:36

今天在看Boost的scoped_ptr的时候了解到了"Opaque Pointer"的概念。关于"Opaque Pointer"的定义以及作用,可以参见wiki。

但是在使用"Opaque Pointer"的时候,需要注意两处陷阱。

我们首先来看一下"Opaque pointer"的示例代码:

// header file:class Handle {private:    struct CheshireCat;                CheshireCat *smile;            public:    Handle();                          ~Handle();                        // Other operations...};
// cpp file:#include "handle.h" struct Handle::CheshireCat {    CheshireCat() {    }    ~CheshireCat() {    }}; Handle::Handle(): smile(new CheshireCat()) {} Handle::~Handle() {    delete smile;}

第一个陷阱就是,这个程序对于异常是不安全的。如果在CheshireCat的构造函数中抛出了异常,那么会导致Handle的构造函数异常终止。进而会导致Handle的析构函数不会被执行,从而也就造成了内存泄漏。这个陷阱还是比较明显的。解决办法可以使用标准库提供的"std::auto_ptr"或者Boost提供的"boost::scoped_ptr"等“智能指针”来对裸指针进行包装。当智能指针对象析构的时候,会自动的释放它所管理的指针。从而避免了由于发生异常而导致的内存泄漏。

第二个陷阱相对来说就不那么明显了。它是由“类型前置声明”引起的,即我们这里的"CheshireCat"。下面代码为使用智能指针后的代码:

// header file:class Handle {private:    struct CheshireCat;                std::auto_ptr<CheshireCat> smile;   public:    Handle();                                              // Other operations...};
// cpp file:#include "handle.h" struct Handle::CheshireCat {    CheshireCat() {    }    ~CheshireCat() {    }}; Handle::Handle(): smile(new CheshireCat()) {}

看上去貌似没什么问题,由于智能指针会帮我们管理裸指针,所以我们不需要自己定义析构函数来delete指针。一切看似那么美好,但如果我们像上述代码那样省略析构函数的话,我们就中了第二个陷阱了。因为如果我们不定义自己的析构函数的话,编译器会自动帮我们生成一个。但是当编译器自动生成析构函数的时候,CheshireCat的类型是未知的,只是一个前置声明而已。所以在自动生成的析构函数中,当删除CheshireCat对象的时候,编译器不会调用它的析构函数(大家可以通过在CheshireCat的析构函数中添加输出语句来验证下它的析构函数到底有没有被调用),而只是会回收它所占用的内存空间,因为编译器根本不知道CheshireCat是个什么东东。

这样就会存在潜在的问题。如果CheshireCat中的成员变量只是像int这种的基本类型还好。因为对于他们的清理只要删除他们所占用的内存空间即可。但是如果是一些复合类型(如Object),或者指针类型(如char*),那么问题就大了,如代码所示:

struct Handle::CheshireCat {    char* buffer;    Object object;    CheshireCat() : buffer(new char[1024] {    }    ~CheshireCat() {        delete[] buffer;    }};

因为CheshireCat的析构函数不会被调用,那么在CheshireCat构造函数中new出来的char数组也就不会被delete,同时成员变量object的析构函数也不会被调用,这样会导致严重的问题。

还好Boost的scoped_ptr的作者考虑到了这些问题。如果程序员像上述那样忘记自己定义析构函数了,那么在编译的时候就会出现编译错误(error: invalid application of ‘sizeof’ to incomplete type ‘Handle::CheshireCat’)。而不会像auto_ptr那样编译没问题,只有在运行后才可能发现问题。

那么scoped_ptr是如何做到这种检测的呢?下面是它析构函数代码的一部分:

typedef char type_must_be_complete[ sizeof(T)? 1: -1 ]

如果在编译scoped_ptr的析构函数的时候,其所指向的对象类型(即代码中的type_must_be_complete)未知,那么sizeof(type_must_be_complete)就会产生编译错误。这样就避免了上述问题的发生。增加了程序的可靠性。

原创粉丝点击