2.设计原理
假设你需要一个C++程序计算两点间的距离,你可能会定义一个结构体:
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);
}
非常简单,它是可用的,但不是通用的。对于库来说,它必须进一步设计。上述设计只能用于笛卡尔坐标系中的二维点、结构mypoint(而不是其他结构)。通用库应能够计算距离:
-
对于任何point类或结构,而不仅仅是此mypoint类型
-
在两个以上的维度上
-
对于其他坐标系,例如地球上方或球体上
-
点与线之间或其他几何图形组合之间
-
以比双精度更高的精度
-
避免平方根:我们通常不想这样做,因为这是一个相对昂贵的函数,而比较距离则没有必要
在本节和后续章节中,我们将逐步使设计更通用。
2.1使用模板
距离函数可以变成一个模板函数。这很简单,可以计算除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++类,其中成员变量受到保护…这样的类不允许直接访问x和y成员。所以,这一段很短,我们继续。
2.2使用Traits
我们需要采用一种通用方法,允许任何点类型作为距离函数的输入。我们将使用traits系统添加几个级别的间接寻址,而不是访问x和y成员。然后,该函数变为:
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函数,用维度作为模板参数,以访问点的坐标。这将转到traits系统,定义如下:
namespace traits
{
template <typename P, int D>
struct access {};
}
然后专门用于mypoint类型,实现一个名为get的静态方法:
namespace traits
{
template <>
struct access<mypoint, 0>
{
static double get(mypoint const& p)
{
return p.x;
}
};
// same for 1: p.y
...
}
调用traits::access<mypoint,0>::get(a)现在返回x坐标。很好,不是吗?对于库中经常使用的函数来说,它太冗长了。我们可以通过添加额外的免费函数来缩短语法:
template <int D, typename P>
inline double get(P const& p)
{
return traits::access<P, D>::get(p);
}
这使我们能够为任何具有traits::access专门化的点调用get<0>(a),如本段开头的距离算法所示。因此,我们希望使用x()之类的方法来启用类,只要access结构有专门化,并且有一个静态get函数为维度0返回x(),为维度1和y()返回类似的x(),就可以支持这些类。
Traits?
C++ traits技术浅谈
2.3维度不可知论
现在我们可以计算2D中点之间的距离,任何结构或类的点。然而,我们也想要3D。因此,我们必须使其维度不可知。这使我们的距离函数变得复杂。我们可以使用for循环遍历维度,但是for循环有另一种性能,而不是最初直接添加坐标。但是,我们可以更多地使用模板,并使距离算法如下,更复杂,但对模板迷有吸引力:
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));
}
上面提到的dimension,使用traits类定义:
namespace traits
{
template <typename P>
struct dimension {};
}