一些本不应在一个库里出现的有用的东西,只是因为它们每个都不太复杂和广泛,不足够形成一个单独的库。但不是说它们没有什么用外;事实上小的工具通常都有最广泛的用处。在Boost, 这些小工具被集中起来,形成一个称为Utility的库。你可以在这找到checked_delete, 一个函数,用于确认在删除点的类型是完整的;还有类noncopyable,用于确保类不能被复制;还有enable_if,用于对函数重载的完全控制。
Utility 库如何改进你的程序?
- 编译期断言 BOOST_STATIC_ASSERT
- 安全的析构 checked_delete 和 checked_array_delete
- 禁止复制 noncopyable
- operator&被重载时用 addressof取得对象地址
- 用enable_if 和 disable_if控制重载与特化
有些工具还不够组成它们自己的库,因此它们与其它实体被集合到一起。这就形成了 Boost.Utility,收集了一些没有更合适地方存放的、有用的工具。它们很有用,应该被加入到Boost,但它们又太小,不足以形成自己的库。本文介绍Boost.Utility中最基本的以及最广泛使用的工具。
我们将从 BOOST_STATIC_ASSERT开始,它是一个在编译期判断整型常量表达式的工具。然后,我们看看当你通过一个指向不完整类型的指针delete对象时,即当被删除的对象的内存布局未知时,会发生什么。checked_delete 使得这个讨论更为有趣。我们还会看到 noncopyable 如何防止一个类被复制,这也是本章最重要的主题。然后我们将看到 addressof, 它用于阻止那些重载了operator&的险恶的程序员的病态行为。最后,我们将测试 enable_if, 它非常有用,可用于在名字查找时控制函数重载与模板特化是否被考虑。
BOOST_STATIC_ASSERT
头文件: "boost/static_assert.hpp"
在运行期执行断言可能是你经常用到的,也是非常合理的。它是测试前置条件、后置条件以及不变式的好方法。执行运行期断言有很多不同的方法,但是在编译期你如何进行断言呢?当然,唯一的方法就是让编译器产生一个错误,这是很平常的事情(我在无意中都做过几千次了),但如何从错误信息中获得有意义的信息却不是那么明显的。而且,即使你在一个编译器上找到了办法,也很难把它移植到其它编译器上。这就是使用 BOOST_STATIC_ASSERT的原因。它可以在不同的平台上使用,正如我们即将看到的。
用法
要开始使用静态断言,就要包含头文件 "boost/static_assert.hpp". 该头文件定义了宏BOOST_STATIC_ASSERT. 作为它的第一个使用范例,我们来看看如何在类作用域中使用它。考虑一个泛化的类,它要求实例化时所用的类型是一个整数类型。我们不想为所有类型提供特化,因此我们需要在编译期进行测试,以确保我们的类的确是用一个整数类型进行实例化的。现在,我们先提前一点使用另一个Boost库来进行测试,它就是 Boost.Type_traits. 我们使用一个称为is_integral的断言,它对它的参数执行一个编译期求值,正如你从它的名字可以猜到的一样,求值的结果是表明该类型是否一个整数类型。
#include <iostream>
#include "boost/type_traits.hpp"
#include "boost/static_assert.hpp"
template <typename T> class only_compatible_with_integral_types {
BOOST_STATIC_ASSERT(boost::is_integral<T>::value);
};
有了这个断言,在实例化类 only_compatible_with_integral_types 时如果试图使用一个非整型的类型,就会导致一个编译期的失败。输出信息取决于编译器,但在多数编译器下输出信息会惊人地一致。
假设我们试图这样实例化:
- only_compatible_with_integral_types<double> test2;
Error: use of undefined type
'boost::STATIC_ASSERTION_FAILURE<false>'
在类的作用域里,你可以明确类的要求:象在前面这样的模板中明确参数的类型就是一个明显的例子。你也可以使用断言来明确类所要求的其它前提条件,如类型的大小等等。
函数作用域中的BOOST_STATIC_ASSERT
BOOST_STATIC_ASSERT 也可以用在函数作用域中。例如,考虑一个泛化的函数,它带有一个非类型模板参数,并且该参数只接受1至10的值。与其在运行期执行断言,我们不如在编译器使用静态断言。
template <int i> void accepts_values_between_1_and_10() {
BOOST_STATIC_ASSERT(i>=1 && i<=10);
}
该函数的用户不能使用超出允许范围的数值来实例化这个函数。当然,断言中的表达式必须是一个纯粹的编译期表达式,也就是说,表达式中的参数和操作符都必须被编译器所认识。BOOST_STATIC_ASSERT 当然并不是只能用于泛型函数;我们可以在任何函数中很方便地测试条件。例如,一个函数需要一个与平台相关的前提条件,就常常需要一个断言。
void expects_ints_to_be_4_bytes() {
BOOST_STATIC_ASSERT(sizeof(int)==4);
}
总结
你所看到的这种静态断言在C++中正变得象运行期断言assert那样常用。这应该至少部分地归功于"元编程革命",它使得一个程序中更多的计算量在编译期执行。表达编译期断言的唯一方法就是让编译器产生一个错误。为了让断言可用,错误提示必须可以传达有用的信息,但这很难做到可移植(事实上,根本不可能做到)。这正是 BOOST_STATIC_ASSERT 所要做的,它在大多数的编译器下提供了编译期断言的一致输出。它可用于名字空间、类、函数以及作用域。
以下情形下使用 BOOST_STATIC_ASSERT :
- 当条件可以在编译期进行求值
- 对类型的要求可以在编译期表示
- 你需要对两个或以上的整型常量间的关系进行断言
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<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