我们将从 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
#include "boost/type_traits.hpp"
#include "boost/static_assert.hpp"
template class only_compatible_with_integral_types {
BOOST_STATIC_ASSERT(boost::is_integral::value);
};
Error: use of undefined type
'boost::STATIC_ASSERTION_FAILURE'
template void accepts_values_between_1_and_10() {
BOOST_STATIC_ASSERT(i>=1 && i<=10);
}
void expects_ints_to_be_4_bytes() {
BOOST_STATIC_ASSERT(sizeof(int)==4);
}
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.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
// 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
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);
}
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;
}
noncopyable
头文件: "boost/utility.hpp"
通常编译器都是程序员的好朋友,但并不总是。它的好处之一在于它会自动为我们提供复制构造函数和赋值操作符,如果我们决定不自己动手去做的话。这也可能会导致一些不愉快的惊讶,如果这个类本身就不想被复制(或被赋值)。如果真是这样,我们就需要明确地告诉这个类的使用者复制构造以及赋值是被禁止的。我不是说在代码中进行注释说明,而是说要禁止对复制构造函数以及赋值操作符的访问。幸运的是,当类带有不能复制或不能赋值的基类或成员函数时,编译器生成的复制构造函数及赋值操作符就不能使用。boost::noncopyable 的工作原理就是禁止访问它的复制构造函数和赋值操作符,然后使用它作为基类。
用法
要使用 boost::noncopyable, 你要从它私有地派生出不可复制类。虽然公有继承也可以,但这是一个坏习惯。公有继承对于阅读类声明的人而言,意味着IS-A (表示派生类IS-A 基类)关系,但表明一个类IS-A noncopyable 看起来有点不太对。要从noncopyable派生,就要包含 "boost/utility.hpp" 。
#include "boost/utility.hpp"
class please_dont_make_copies : boost::noncopyable {};
int main() {
please_dont_make_copies d1;
please_dont_make_copies d2(d1);
please_dont_make_copies d3;
d3=d1;
}
noncopyable.hpp: In copy constructor
' please_dont_make_copies::please_dont_make_copies (const please_dont_make_copies&)':
boost/noncopyable.hpp:27: error: '
boost::noncopyable::noncopyable(const boost::noncopyable&)' is
private
noncopyable.cpp:8: error: within this context
boost/noncopyable.hpp: In member function 'please_dont_make_copies&
please_dont_make_copies::operator=(const please_dont_make_copies&)':
boost/noncopyable.hpp:28: error: 'const boost::noncopyable&
boost::noncopyable::operator=(const boost::noncopyable&)' is private
noncopyable.cpp:10: error: within this context
class please_dont_make_copies {
public:
void do_stuff() {
std::cout <<
"Dear client, would you please refrain from copying me?";
}
};
please_dont_make_copies p1;
please_dont_make_copies p2(p1);
please_dont_make_copies p3;
p3=p2;
class please_dont_make_copies {
public:
please_dont_make_copies() {}
void do_stuff() {
std::cout <<
"Dear client, would you please refrain from copying me?";
}
private:
please_dont_make_copies(const please_dont_make_copies&);
please_dont_make_copies& operator=
(const please_dont_make_copies&);
};
#include "boost/utility.hpp"
class please_dont_make_copies : boost::noncopyable {
public:
void do_stuff() {
std::cout << "Dear client, you just cannot copy me!";
}
};
class full_of_errors {
int* value_;
public:
full_of_errors() {
value_=new int(13);
}
~full_of_errors() {
delete value_;
}
};
full_of_errors f1;
full_of_errors f2(f1);
full_of_errors f3=f2;
full_of_errors f4;
f4=f3;
class not_full_of_errors {
int* value_;
public:
not_full_of_errors() {
value_=new int(13);
}
not_full_of_errors(const not_full_of_errors& other) :
value_(new int(*other.value_)) {}
not_full_of_errors& operator=
(const not_full_of_errors& other) {
*value_=*other.value_;
return *this;
}
~not_full_of_errors() {
delete value_;
}
};
addressof
头文件: "boost/utility.hpp"
要取得一个变量的地址,我们要依赖于返回的值是否真的是这个变量的地址。但是,技术上重载operator&是有可能的,这意味着存有恶意的人可以破坏你的地址相关的代码。boost::addressof 被用于获得变量的地址,不管取址操作符是否被误用。通过使用一些灵巧的内部机制,模板函数 addressof 确保可以获得真实的对象及其地址。
用法
为确保获得一个对象的真实地址,你要使用 boost::addressof. 它定义在 "boost/utility.hpp". 它常用于原本要使用 operator& 的地方,它接受一个参数,该参数为要获得地址的那个对象的引用。
#include "boost/utility.hpp"
class some_class {};
int main() {
some_class s;
some_class* p=boost::addressof(s);
}
class codebreaker {
public:
int operator&() const {
return 13;
}
};
template void print_address(const T& t) {
std::cout << "Address: " << (&t) << '\n';
}
int main() {
codebreaker c;
print_address(c);
}
template void print_address(const T& t) {
std::cout << "&t: " << (&t) << '\n';
std::cout << "addressof(t): " << boost::addressof(t) << '\n';
}
&t: 13
addressof(t): 0012FECB13
enable_if
头文件: "boost/utility/enable_if.hpp"
有时候,我们希望控制某个函数或类模板的特化是否可以加入到重载决议时使用的重载或特化的集合中。例如,考虑一个重载的函数,它有一个版本是带一个int参数的普通函数,另一个版本是一个函数模板,它要求参数类型 T 具有一个名为type的嵌套类型。它们看起来可能象这样:
void some_func(int i) {
std::cout << "void some_func(" << i << ")\n";
}
template void some_func(T t) {
typename T::type variable_of_nested_type;
std::cout <<
"template void some_func(" << t << ")\n";
}
#include
void some_func(int i) {
std::cout << "void some_func(" << i << ")\n";
}
template void some_func(T t) {
typename T::type variable_of_nested_type;
std::cout <<
"template void some_func(" << t << ")\n";
}
int main() {
int i=12;
short s=12;
some_func(i);
some_func(s);
}
enable_if_sample1.cpp: In function 'void some_func(T)
[with T = short int]':
enable_if_sample1.cpp:17: instantiated from here
enable_if_sample1.cpp:8: error:
'short int' is not a class, struct, or union type
Compilation exited abnormally with code 1 at Sat Mar 06 14:30:08
template typename T::type* some_func(T t) {
typename T::type variable_of_nested_type;
std::cout <<
"template void some_func(" << t << ")\n";
return 0;
}
class some_class {
public:
typedef int type;
};
int main() {
int i=12;
short s=12;
some_func(i);
some_func(s);
some_func(some_class());
}
void some_func(12)
void some_func(12)
templatevoid some_func(T t)
template
void some_func(T t,typename T::type* p=0) {
typename T::type variable_of_nested_type;
std::cout << "template void some_func(T t)\n";
}
#include
#include "boost/utility/enable_if.hpp"
#include "boost/type_traits.hpp"
void some_func(int i) {
std::cout << "void some_func(" << i << ")\n";
}
template void some_func(
T t,typename boost::disable_if<
boost::is_integral >::type* p=0) {
typename T::type variable_of_nested_type;
std::cout << "template void some_func(T t)\n";
}
#include
#include "boost/utility/enable_if.hpp"
#include "boost/type_traits.hpp"
#include "boost/mpl/has_xxx.hpp"
BOOST_MPL_HAS_XXX_TRAIT_DEF(type)
void some_func(int i) {
std::cout << "void some_func(" << i << ")\n";
}
template void some_func(T t,
typename boost::enable_if >::type* p=0) {
typename T::type variable_of_nested_type;
std::cout << "template void some_func(T t)\n";
}
template typename
boost::enable_if,void>::type
some_func(T t) {
typename T::type variable_of_nested_type;
std::cout << "template void some_func(T t)\n";
}
template class some_class {
public:
int max() const {
std::cout << "some_class::max() for the primary template\n";
return std::numeric_limits::max();
}
};
template class some_class {
public:
int max() const {
std::cout << "some_class::max() for the primary template\n";
return std::numeric_limits::max();
}
};
template class some_class >::type> {
public:
T max() const {
std::cout << "some_class::max() with an arithmetic type\n";
return std::numeric_limits::max();
}
};
#include
#include
#include
#include "boost/utility/enable_if.hpp"
#include "boost/type_traits.hpp"
// Definition of the template some_class omitted
int main() {
std::cout << "Max for std::string: " <<
some_class().max() << '\n';
std::cout << "Max for void: " <<
some_class().max() << '\n';
std::cout << "Max for short: " <<
some_class().max() << '\n';
std::cout << "Max for int: " <<
some_class().max() << '\n';
std::cout << "Max for long: " <<
some_class().max() << '\n';
std::cout << "Max for double: " <<
some_class().max() << '\n';
}
some_class::max() for the primary template
Max for std::string: 2147483647
some_class::max() for the primary template
Max for void: 2147483647
some_class::max() with an arithmetic type
Max for short: 32767
some_class::max() with an arithmetic type
Max for int: 2147483647
some_class::max() with an arithmetic type
Max for long: 2147483647
some_class::max() with an arithmetic type
Max for double: 1.79769e+308
enable_if 和 enable_if_c原则上并没有不同。它们的区别仅在于是否要求有嵌套类型value。
总结
被称为SFINAE的C++语言特性是很重要的。没有它,很多新的代码会破坏已有的代码,并且某些类型的函数重载(以及模板特化)将会无法实现。直接使用SFINAE来控制特定的函数或类型,使之被允许或被禁止用于重载决议,会很复杂。这样也会产生难以阅读的代码。使用 boost::enable_if 是更好的办法,它可以规定重载仅对某些特定类型有效。如果相同的参数用于 disable_if, 则规定重载对于符合条件的类型无效。虽然使用SFINAE也可以实现,但该库可以更好地表达相关意图。本章忽略了enable_if 和 disable_if的lazy版本(名为 lazy_enable_if 和 lazy_disable_if), 不过我在这里简单地提及一下。lazy版本被用于避免实例化类型可能无效的情形(取决于条件的取值).
以下情形时使用 enable_if :