checked_delete

优质
小牛编辑
174浏览
2023-12-01

头文件: "boost/checked_delete.hpp"

通过指针来删除一个对象时,执行的结果取决于执行删除时被删除的类型是否可知。对一个指向不完整类型的指针执行delete几乎不可能有编译器警告,这会导致各种各样的麻烦,由于析构函数可以没有被执行。换句话说,即进行清除的代码没有被执行。checked_delete 在对象析构时执行一个静态断言,测试类是否可知,以确保析构函数被执行。

用法

checked_delete 是一个boost名字空间中的模板函数。它用于删除动态分配的对象,对于动态分配的数组,同样有一个称为 checked_array_delete的模板函数。这些函数接受一个参数:要删除的指针,或是要删除的数组。这两个函数都要求在销毁对象时(即对象被传给函数时),这些被删除的类型必须是可知的。使用这些函数,要包含头文件"boost/checked_delete.hpp". 使用这些函数时,你只需象调用delete那样简单地调用它们。以下程序前向声明了一个类some_class, 而没有定义它。有些编译器允许对一个指向 some_class 的指针被删除(稍后再讨论这个),但使用 checked_delete 后,就不能通过编译了,除非有一个 some_class 的定义。

#include "boost/checked_delete.hpp"

class some_class;

some_class* create() {
  return (some_class*)0;
}

int main() {
  some_class* p=create();
  boost::checked_delete(p2);
}

如果你试图编译这段代码,对函数 checked_delete<some_class> 的实例化将失败,因为 some_class 是一个不完整的类型。你的编译器会输出类似下面的信息:

checked_delete.hpp: In function 'void
boost::checked_delete(T*) [with T = some_class]':
checked_sample.cpp:11:   instantiated from here
boost/checked_delete.hpp:34: error: invalid application of 'sizeof' to an incomplete type
boost/checked_delete.hpp:34: error: creating array with
size zero ('-1')
boost/checked_delete.hpp:35: error: invalid application of
'sizeof' to an incomplete type
boost/checked_delete.hpp:35: error: creating array with
size zero ('-1')
boost/checked_delete.hpp:32: warning: 'x' has incomplete type

错误信息的前面部分清楚地说明了问题:checked_delete 遇到了一个不完整的类型。但我们的代码中哪里存在不完整的类型呢?接下来的章节我们来讨论它。

究竟是什么问题?

在我们深入了解 checked_delete的好处之前,让我们先来彻底弄清楚问题所在。如果你试图删除一个指针,而该指针指向的是一个带有非平凡析构函数[4]的不完整类型[3],结果将是未定义的行为。这是如何发生的呢?让我们来看一个例子。

[3] 不完整的类型是指已声明但未定义的类型。

[4] 标准说法是,类的一个或多个直接基类,或者一个或多个非静态数据成员,具有用户定义的析构函数。

// deleter.h
class to_be_deleted;

class deleter { 
public:
  void delete_it(to_be_deleted* p);
};

// deleter.cpp
#include "deleter.h"

void deleter::delete_it(to_be_deleted* p) {
  delete p;
}

// to_be_deleted.h
#include <iostream>
class to_be_deleted
{
public:
  ~to_be_deleted() {
    std::cout << 
      "I'd like to say important things here, please.";
  }
};

// Test application
#include "deleter.h"
#include "to_be_deleted.h"

int main() {
  to_be_deleted* p=new to_be_deleted;

  deleter d;
  d.delete_it(p);
}

以上代码试图 delete 一个指向不完整类型to_be_deleted的指针,这会导致未定义行为。注意,to_be_deleteddeleter.h中是前向声明的;deleter.cpp 包含了 deleter.h 而没有包含 to_be_deleted.h: 而to_be_deleted.h 中为to_be_deleted定义了一个非平凡析构函数。这种麻烦很容易出现,尤其是在使用智能指针的时候。我们要做的就是在调用delete时确认类型是完整的,这正是 checked_delete 所做的。

checked_delete 来解决问题

前面的例子说明了删除不完整类型时不进行确认很可能会引起麻烦,而且不是所有编译器会对此给出警告。编写泛型代码时,避免这种情况是非常必要的。使用 checked_delete重写这个例子,你只需要把 delete p 改为 checked_delete(p).

void deleter::do_it(to_be_deleted* p) {
  boost::checked_delete(p);
}

checked_delete 基本上就是一个判断类是否完整的断言,它的实现如下:

template< typename T > inline void checked_delete(T * x) {
  typedef char type_must_be_complete[sizeof(T)];
  delete x;
}

这里的想法是创建一个char的数组,数组的元素数量为T的大小。如果 checked_delete 被一个不完整的类型 T 所实例化,编译将会失败,因为 sizeof(T) 会返回 0, 而创建一个0个元素的(自动)数组是非法的。你也可以用 BOOST_STATIC_ASSERT 来执行这个断言。

BOOST_STATIC_ASSERT(sizeof(T));

在编写要求使用完整类型进行实例化的模板时,这个工具非常方便。对于数组,也有一个相应的"checked deleter",称为 checked_array_delete, 它的用法类似于 checked_delete.

to_be_deleted* p=new to_be_deleted[10];
boost::checked_array_delete(p);

总结

删除一个动态分配的对象时,必须调用它的析构函数。如果这个类型是不完整的,即只有声明没有定义,那么析构函数可能会没被调用。这是一种潜在的危险状态,所以应该避免它。对于类模板及函数模板,风险会更大,因为无法预先知道会使用什么类型。使用 checked_deletechecked_array_delete, 可以解决这个删除不完整类型的问题。它没有运行期的额外开销,只是直接调用 delete, 因此说 checked_delete 带来的安全性实际上是免费的。

如果你需要在调用delete时确保类型是完整的,就使用 checked_delete