🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## checked_delete ### 头文件: `"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&lt;some_class&gt;` 的实例化将失败,因为 `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_deleted` 在 `deleter.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_delete` 和 `checked_array_delete`, 可以解决这个删除不完整类型的问题。它没有运行期的额外开销,只是直接调用 `delete`, 因此说 `checked_delete` 带来的安全性实际上是免费的。 如果你需要在调用`delete`时确保类型是完整的,就使用 `checked_delete` 。