假设你需要使用c++程序来计算两点间的距离.你可能会这样做:
struct
:struct mypoint
{
double x, y;
};
double distance(mypoint const& a, mypoint const& b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
相当简单而实用,但是不够通用.一个库的设计需要考虑未来可能的变化.
上面的设计只能用于笛卡尔坐标系中的2D点.
通用的库需要能够计算如下距离:
point struct
或者point class
,而不是只适用于mypoint
.点与线
或者其它几何图形
之间的距离double
更高的精度sqrt
:通常我们不希望调用它,因为它的开销比较大.而且对于比较距离时没有必要.接下来,我们将一步一步给出一个更通用的实现.
我们可以将距离函数改为模板函数.这样就可以计算除mypoint
之外的其他点类型之间的距离.
我们添加两个模板参数,允许输入两种不同的点类型.
template <typename P1, typename P2>
double distance(P1 const& a, P2 const& b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return std::sqrt(dx * dx + dy * dy);
}
模板版本
比之前的实现好一些,但是还不够.
考虑c++类
的成员变量为protected
或者不能直接访问x
,y
.
Traits
我们需要使用一种更通用的方法来允许任意的点类型都能够作为距离函数的输入.
除了直接访问x
和y
,我们将添加一层间接层,使用traits
系统.
距离函数可以变为:
template <typename P1, typename P2>
double distance(P1 const& a, P2 const& b)
{
double dx = get<0>(a) - get<0>(b);
double dy = get<1>(a) - get<1>(b);
return std::sqrt(dx * dx + dy * dy);
}
上面的距离函数使用了get
函数来访问一个点的坐标系统,使用点的维度
作为模板参数.
get
可以这样实现:
namespace traits
{
template <typename P, int D>
struct access {};
}
定义mypoint
的模板特例:
namespace traits
{
template <>
struct access<mypoint, 0>
{
static double get(mypoint const& p)
{
return p.x;
}
};
// same for 1: p.y
...
}
现在通过调用traits::access
就可以返回坐标系中的x
.我们可以通过定义get
来进一步简化调用方式:
template <int D, typename P>
inline double get(P const& p)
{
return traits::access<P, D>::get(p);
}
通过上面的实现,我们就可以对任何特化了traits::access
的point a
调用get<0>(a)
.
同样的原理,我们也可以实现对于坐标y
的get<1>(a)
.
为了实现对任意维度的计算,我们可以通过循环来遍历所有维度.但是循环调用相对于直接计算会有性能开销.因此我们可以通过使用模板实现这样的算法:
template <typename P1, typename P2, int D>
struct pythagoras
{
static double apply(P1 const& a, P2 const& b)
{
double d = get<D-1>(a) - get<D-1>(b);
return d * d + pythagoras<P1, P2, D-1>::apply(a, b);
}
};
template <typename P1, typename P2 >
struct pythagoras<P1, P2, 0>
{
static double apply(P1 const&, P2 const&)
{
return 0;
}
};
然后距离函数
可以调用pythagoras
并指定维度:
template <typename P1, typename P2>
double distance(P1 const& a, P2 const& b)
{
BOOST_STATIC_ASSERT(( dimension<P1>::value == dimension<P2>::value ));
return sqrt(pythagoras<P1, P2, dimension<P1>::value>::apply(a, b));
}
维度可以通过定义另外一个traits
类来实现:
namespace traits
{
template <typename P>
struct dimension {};
}
然后针对相应的类(如mypoint
)进行特例化,因为这个traits
只是发布一个值,因此为了简便我们可以继承Boost.MPL
中的class boost::mpl::int_
:
namespace traits
{
template <>
struct dimension<mypoint> : boost::mpl::int_<2>
{};
}
现在我们就实现了对任意维度点进行计算距离的算法.我们还使用编译期断言来防止对两个不同维度的点进行计算.
在上面的实现中,我们假设了double
类型,如果点是integer
呢?
namespace traits
{
template <typename P>
struct coordinate_type{};
// specialization for our mypoint
template <>
struct coordinate_type<mypoint>
{
typedef double type;
};
}
和access
函数类似,我们同样添加一个代理:
template <typename P>
struct coordinate_type : traits::coordinate_type<P> {};
然后我们可以修改我们的距离计算函数.因为计算的两个point类型可能有不同的类型,我们必须处理这种情况.我们需要选择其中一种具有更高精度的类型作为结果类型,我们假设有一个select_most_precise
元函数用于选择最佳类型.
这样我们的计算函数可以改为:
template <typename P1, typename P2, int D>
struct pythagoras
{
typedef typename select_most_precise
<
typename coordinate_type<P1>::type,
typename coordinate_type<P2>::type
>::type computation_type;
static computation_type apply(P1 const& a, P2 const& b)
{
computation_type d = get<D-1>(a) - get<D-1>(b);
return d * d + pythagoras <P1, P2, D-1> ::apply(a, b);
}
};