BOOST_STATIC_ASSERT

头文件: "boost/static_assert.hpp"

在运行期执行断言可能是你经常用到的,也是非常合理的。它是测试前置条件、后置条件以及不变式的好方 法。执行运行期断言有很多不同的方法,但是在编译期你如何进行断言呢?当然,唯一的方法就是让编译器产生一个错误,这是很平常的事情(我在无意中都做过几 千次了),但如何从错误信息中获得有意义的信息却不是那么明显的。而且,即使你在一个编译器上找到了办法,也很难把它移植到其它编译器上。这就是使用 BOOST_STATIC_ASSERT的原因。它可以在不同的平台上使用,正如我们即将看到的。

用法

要开始使用静态断言,就要包含头文件 "boost/static_assert.hpp". 该头文件定义了宏[2] BOOST_STATIC_ASSERT. 作为它的第一个使用范例,我们来看看如何在类作用域中使用它。考虑一个泛化的类,它要求实例化时所用的类型是一个整数类型。我们不想为所有类型提供特化, 因此我们需要在编译期进行测试,以确保我们的类的确是用一个整数类型进行实例化的。现在,我们先提前一点使用另一个Boost库来进行测试,它就是 Boost.Type_traits. 我们使用一个称为is_integral的断言,它对它的参数执行一个编译期求值,正如你从它的名字可以猜到的一样,求值的结果是表明该类型是否一个整数类型。

[2] 是的,它是一个宏。你知道,宏也可以很有用的。

#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

  • 当条件可以在编译期进行求值

  • 对类型的要求可以在编译期表示

  • 你需要对两个或以上的整型常量间的关系进行断言