以下代码来源于《深入实践Boost:Boost程序库开发的94个秘笈》一书
介绍Boost库用在编译时检查、调优算法以及其他元编程任务的一些基本例子。
为什么需要关心编译时的事呢?
因为程序的发行版本是编译一次并运行多次的,在编译时做得越多,运行时要做的剩余工作就越少,从而程序运行得更快更可靠。
在编译时检查大小
假设要编写一个在指定大小的缓冲区中存储值的序列化函数:
#include <string>
#include <boost/array.hpp>
template <class T,std::size_t BufSizeV>
void serialize(const T& value, boost::array<unsigned char, BufSizeV>& buffer)
{
// TODO:修复
std::memcpy(&buffer[0], &value, sizeof(value));
}
这段代码未检查缓冲区大小,所以可能会溢出。可能被用于非纯数据(POD)类型,这将导致不正确的行为。
也许,可以增加一些断言解决部分问题,例如:
void serialize(const T& value, boost::array<unsigned char, BufSizeV>& buffer)
{
assert(BufSizeV >= sizeof(value));
// TODO:修复
std::memcpy(&buffer[0], &value, sizeof(value));
}
BufSizeV和sizeof(value)的值在编译时已知,所以如果缓冲区过小,这段代码应该在编译失败,而不是有一个运行时断言。(如果未调用函数,可能不会在调试中触发它,甚至可能在发布模式被优化掉)
更正:
使用Boost.StaticAssert和Boost.TypeTraits库
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_pod.hpp>
template <class T,std::size_t BufSizeV>
void serialize(const T& value, boost::array<unsigned char, BufSizeV>& buffer)
{
BOOST_STATIC_ASSERT(BufSizeV >= sizeof(value));
BOOST_STATIC_ASSERT(boost::is_pod<T>::value);
std::memcpy(&buffer[0], &value, sizeof(value));
}
工作原理:
只有当断言表达式可以在编译时求值并隐式转换为bool时,才可以使用BOOST_STATIC_ASSERT的宏,这意味着,可能只使用sizeof()、静态常量和其他常量表达式。如果断言表达式的计算结果为false,BOOST_STATIC_ASSERT将停止编译程序。
在C++11标准中有一个相当于Boost版本的static_assert关键字。
在整数类型中启动模板函数
一个实现了某个功能的模板类如下:
// 通用实现
template <class T>
class data_processor
{
public:
double process(const T& v1, const T& v2, const T& v3) { return 0; }
};
// 整型优化版本
template <class T>
class data_processor
{
public:
double process(int v1, int v2, int v3) { return 1; }
};
// 浮点型SSE优化版
template <class T>
class data_processor
{
public:
double process(double v1, double v2, double v3) { return 2; }
};
如何使编译器自动为指定的类型选择正确的类?
Boost.Utility和Boost.TypeTraits
1.包含头文件:
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/is_float.hpp>
2.为通用实现添加一个额外的带默认值的模板参数:
// 通用实现
template <class T,class Enable = void>
class data_processor
{
public:
double process(const T& v1, const T& v2, const T& v3) { return 0; }
};
3.修改优化版本,使得现在它们被编译器视为模板部分专门化:
// 整型优化版本
template <class T>
class data_processor<T,typename boost::enable_if_c<boost::is_integral<T>::value>::type>
{
public:
double process(int v1, int v2, int v3) { return 1; }
};
// 浮点型SSE优化版
template <class T>
class data_processor<T,typename boost::enable_if_c<boost::is_float<T>::value>::type>
{
public:
double process(double v1, double v2, double v3) { return 2; }
};
4.这样,使用data_processor类,编译器会自动选择正确的类:
template <class T>
double example_func(T v1, T v2, T v3)
{
data_processor<T> proc;
return proc.process(v1, v2, v3);
}
int main()
{
// 将调用整型优化版本
example_func(1, 2, 3);
short s = 0;
example_func(s, s, s);
// 将调用实数类型版本
example_func(1.0, 2.0, 3.0);
example_func(1.0f, 2.0f, 3.0f);
// 将调用通用版
example_func("Hello", "word", "processing");
return 0;
}
boost::enable_if_c使用了SFINAE(替换失败不是一个错误,Substitution Failure Is Not An Error)的原则。这个原则的工作原理是:如果一个无效的参数或返回值是在一个函数或类模板的实例化过程中形成的,就会从重载决议集中删除该实例并且不会导致编译错误。
回到解决方案,如果传递一个int作为T类型,首先,编译器将尝试实例化模板部分专门化,然后再使用非专门化(通用)版本。当它试图实例化一个float版时,boost::is_float<T>::value元函数将返回false。boost::enable_if_c<false>::type元函数不能正确实例化(因为boost::enable_if_c<false> 没有::type),而那就是SFINAE将起作用的地方。因为类模板不能被实例化,而且这必须被解释为不是一个错误,编译器将跳过此模板专门化。其次,匹配实例化整数类型优化成功。所有的专门化实例都失败,编译器将尝试实例化通用版本,而且一定会成功。
如果没有boost::enable_if_c<> ,则所有的部分专门化版本都可以同时对任何类型被实例化,这会导致歧义和编译失败。
boost::enable_if_c的另一个版本是boost::enable_if,它们之间的区别是,enable_if_c接受常数作为模板参数,而enable_if接受一个有value静态成员的对象。例如,boost::enable_if_c<boost::is_integral<T>::value>::type等于boost::enable_if<boost::is_integral<T>>::type>。
C++11在<type_traits> 头文件中定义了一个std::enable_if,行为与boost::enable_if_c相似。
在实数类型中禁用模板函数
// 适用于所有可用的类型
template <class T>
T process_data(const T& v1, const T& v2, const T& v3);
// 优化版,适用有一个operator +=函数的类型
template <class T>
T process_data_plus_assign(const T& v1, const T& v2, const T& v3);
对于以上的例子,我们并不想改变已经写好的代码,而只要有可能,要强制编译器自动用优化函数代替默认的。
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/has_plus_assign.hpp>
// 为带有赋值运算符的类型禁用默认的实现
// process_data通用版本
template <class T>
typename boost::disable_if_c<boost::has_plus_assign<T>::value, T>::type process_data(const T& v1, const T& v2, const T& v3)
{
std::cout << v1 << v2 << v3 << std::endl;
return v1;
}
// 优化版,适用有一个operator +=函数的类型
template <class T>
T process_data_plus_assign(const T& v1, const T& v2, const T& v3)
{
return v1 + v2 + v3;
}
// 为带有赋值运算符的类型启用优化版本
template <class T>
typename boost::enable_if_c<boost::has_plus_assign<T>::value, T>::type process_data(const T& v1, const T& v2, const T& v3)
{
return process_data_plus_assign(v1, v2, v3);
}
int main(){
int i = 1;
// 优化版
process_data(i, i, i);
// 默认版本
// 明确指定模板参数
process_data<const char*>("Testing", "example", "function");
return 0;
}
与enable_if_c和enable_if一样,禁用函数有一个disable_if版本:
template <class T>
typename boost::disable_if<boost::has_plus_assign<T>, T>::type process_data2(const T& v1, const T& v2, const T& v3);
template <class T>
typename boost::enable_if<boost::has_plus_assign<T>, T>::type process_data2(const T& v1, const T& v2, const T& v3);
但是,C++11中既没有disable_if_c也没有disable_if,不过可以使用std::enable_if<!bool_value>::type代替。
从数值创建一个类型
考虑这样一个例子,有一个处理POD数据类型的通用方法:
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_pod.hpp>
// 通用实现
template <class T>
T process(const T& val)
{
BOOST_STATIC_ASSERT((boost::is_pod<T>::value));
// ...
}
而且有分别对大小为1、4和8个字节优化的相同函数。改写处理函数,使得它可以调度对优化版本的调用。
1.定义process_impl函数的通用和优化版本:
#include <boost/mpl/int.hpp>
namespace detail
{
// 通用实现
template <class T, class Tag>
T process_impl(const T& val, Tag /*ignore*/)
{
// ...
}
// 1字节优化实现
template <class T>
T process_impl(const T& val, boost::mpl::int_<1> /*ignore*/)
{
// ...
}
// 4字节优化实现
template <class T>
T process_impl(const T& val, boost::mpl::int_<4> /*ignore*/)
{
// ...
}
// 8字节优化实现
template <class T>
T process_impl(const T& val, boost::mpl::int_<8> /*ignore*/)
{
// ...
}
} // namespace detail 结束
2.编写处理函数:
// 只调用实现
template <class T>
T process(const T& val)
{
BOOST_STATIC_ASSERT((boost::is_pod<T>::value));
return detail::process_impl(val, boost::mpl::int_<sizeof(T)>());
}
这里,boost::mpl::int_<sizeof(T)>(),sizeof(T)在编译时执行,所以它的输出可以用作模板参数。类boost::mpl::int_<> 只是一个空类,它保存整数类型在编译时的值。可以这么实现:
template <int Value>
struct int_
{
static const int value = Value;
typedef int_<Value> type;
typedef int value_type;
};
我们需要这个类的一个实例,所以在boost::mpl::int_<sizeof(T)>()末端有一个圆括号的原因。首先,编译过程中,编译器将尝试匹配拥有第二个参数,而且不是一个模板的函数。如果sizeof(T)是4,编译器将尝试搜索签名像process_impl(T,boost::mpl::int_<4>)的函数,并且会从detail命名空间发现4个字节的优化版本。如果sizeof(T)是34,编译器将无法找到签名像process_impl(T,boost::mpl::int_<34>)
的函数,并且将使用一个模板变体process_impl(const T& val,Tag/*ignore*/)
.
Boost.MPL库有几个用于元编程的数据结构。
- bool_
- int_
- long_
- size_t
- char_
所有的Boost.MPL函数(除了for_each)都是在编译时执行的,并且不会增加运行时的开销。Boost.MPL库不属于C++11。
实现类型特征
如果需要实现一个类型特征,若std::vector类型被作为模板参数传递给它,则返回true。
#include <vector>
#include <boost/type_traits/integral_constant.hpp>
template <class T>
struct is_stdvector : boost::false_type{};
template <class T,class Allocator>
struct is_stdvector<std::vector<T, Allocator>> : boost::true_type {};
boost::true_type类中有一个布尔型::value静态常量,等于true;同理,boost::false_type中也有一个,等于false。并且,这两个类中还有一些类型定义,通常派生自boost::mpl::integral_c,使得它易于将从true_type/false_type派生的类型与Boost.MPL一起使用。
这里,第一个is_stdvector结构是一个通用结构,当没有找到这种结构的模板专门化版本时,它总被使用。第二个is_stdvector结构是一个std::vector类型的专门化模板,且它派生自true::type,所以,当传递一个向量类型给is_stdvector结构体时,专门化模板版本将被使用,否则将使用通用版本,它派生自false_type。
为模板参数选择最佳操作符
假设,我们在使用来自不同厂商的类,它们分别实现了不同数量的算数运算和整数的构造,而现在我们想创建一个传递给它任何类都能递增1的函数。
template <class T>
void inc(T& value)
{
// 调用 ++value;
// 或调用 value++;
// 或 value += T(1);
// 或 value = value + T(1);
}
做法
1.从制作正确的函数化对象开始:
namespace detail
{
struct pre_inc_functor
{
template <class T>
void operator() (T& value) const
{
++value;
}
};
struct post_inc_functor
{
template <class T>
void operator() (T& value) const
{
value++;
}
};
struct plus_assignable_functor
{
template <class T>
void operator() (T& value) const
{
value += T(1);
}
};
struct plus_functor
{
template <class T>
void operator() (T& value) const
{
value = value + T(1);
}
};
}
2.之后,需要一堆类型特征:
#include <boost/type_traits/conditional.hpp>
#include <boost/type_traits/has_plus_assign.hpp>
#include <boost/type_traits/has_plus.hpp>
#include <boost/type_traits/has_post_increment.hpp>
#include <boost/type_traits/has_pre_increment.hpp>
3.准备推断正确的仿函数并使用:
template <class T>
void inc(T& value)
{
typedef detail::plus_functor step_0_t;
typedef typename boost::conditional<boost::has_plus_assign<T>::value, detail::plus_assignable_functor, step_0_t>::type step_1_t;
typedef typename boost::conditional<boost::has_post_increment<T>::value, detail::post_inc_functor, step_1_t>::type step_2_t;
typedef typename boost::conditional<boost::has_pre_increment<T>::value, detail::pre_inc_functor, step_2_t>::type step_3_t;
step_3_t()(value); //默认构造仿函数 调用一个仿函数的operator()
}
这里,实现的重点是conditional<bool Condition,class T1,class T2>元函数,当这个元函数接受true作为第一个参数时,它通过::type typedef返回T1。当boost::conditional元函数接受false作为第一个参数时,它通过::type typedef返回T2,类似if语句那样。
所以,step_0_t持有一个detail::plus_functor元函数,而step_1_t将持有step_0_t或者detail::plus_assignable_functor,step_2_t将持有step_1_t或者detail::post_inc_functor,step_3_t将持有step_2_t或者detail::pre_inc_functor。
也可以用boost::mpl::if_来改写。
在C++03中获取一个表达式的类型
boost::bind是一良好且有用的工具,但是,在C++03中很难作为一个变量储存boost::bind元函数的仿函数。
#include <functional>
#include <boost/bind.hpp>
const ? ? ? var = boost::bind(std::plus<int>(), _1, _1);
在C++11中,可以使用auto关键字代替???,但在C++03中无法做到。
用Boost.Typeof库获得表达式的返回类型可以解决这个问题
#include <boost/typeof/typeof.hpp>
// 只是创建一个名称是var的变量,并且表达式的值作为第二参数传递
// var的类型从表达式的类型检测得到
BOOST_AUTO(var, boost::bind(std::plus<int>(), _1, _1));
C++11新标准中有很多关键字用于检测表达式的类型,Boost.Typeof也有用于它们的宏
// 一段C++11代码
typedef decltype(0.5 + 0.5f) type;
// 使用Boost.Typeof改写
typedef BOOST_TYPEOF(0.5 + 0.5f) type;
// 一段C++11代码
template <class T1,class T2>
auto add(const T1& t1, const T2& t2) ->decltype(t1 + t2)
{
return t1 + t2;
}
// 使用Boost.Typeof改写
template <class T1,class T2>
BOOST_TYPEOF_TPL(T1() + T2()) add(const T1& t1, const T2& t2)
{
return t1 + t2;
}
C++11有一种特殊的语法用于在函数声明的结尾指定返回类型。
这点在C++03中无法效仿,所以不能在宏中使用t1+t2变量。