19.6 内核架构的模板技巧
相关章节
在本节中你将学到…
- 内核参数化设计的核心思想
- 类型相等包装器的实现原理
- 延迟计算(Lazy evaluation)的工作机制
- 区间算术在几何计算中的应用
- CGAL内核的高级模板技巧
19.6.1 概念解释
什么是内核参数化设计?
CGAL的内核(Kernel)是几何算法的基石,它定义了几何对象(点、向量、线等)和基本操作(距离计算、方向判断等)。内核参数化设计允许用户选择不同的数值类型和坐标系,而无需修改算法代码。
类比理解: 想象内核是一个厨房的基础设施。你可以选择:
- 燃气灶 vs 电磁炉(不同数值类型:double vs Gmpq)
- 中式厨房 vs 西式厨房(不同坐标系:笛卡尔 vs 齐次)
- 家用厨房 vs 商用厨房(不同精度级别)
但无论选择哪种,厨师(算法)的操作方式保持不变。
内核架构的核心组件
┌─────────────────────────────────────────────────────────────────┐
│ 内核架构概览 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Kernel │───>│ 几何对象 │ │ 函数对象 │ │
│ │ (参数化) │ │ Point_2 │ │ Less_xy_2 │ │
│ │ │ │ Segment_3 │ │ Orientation │ │
│ └──────────────┘ │ Plane_3 │ │ Distance │ │
│ │ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ ┌──────────────┐ ┌──────────────┐ │
│ ┌──────────────┐ │ 数值类型 │ │ 坐标系 │ │
│ │ Type_equality│ │ double │ │ Cartesian │ │
│ │ _wrapper │ │ Gmpq │ │ Homogeneous │ │
│ └──────────────┘ │ Interval │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
19.6.2 为什么需要这些技巧?
问题1:类型循环依赖
// 问题:Point_2需要知道Kernel,Kernel又包含Point_2
template <typename Kernel>
class Point_2 {
// 使用Kernel的功能
};
template <typename FT>
class MyKernel {
typedef Point_2<MyKernel> Point_2; // 循环依赖!
};问题2:精确度与性能的平衡
// 精确计算很慢,近似计算可能出错
double orient(const Point& p, const Point& q, const Point& r) {
return (q.x() - p.x()) * (r.y() - p.y()) -
(q.y() - p.y()) * (r.x() - p.x());
// 浮点误差可能导致错误的几何判断!
}问题3:代码复用
// 相同的算法需要支持:
// - Cartesian<double>(快速但不精确)
// - Cartesian<Gmpq>(精确但慢)
// - Homogeneous<leda_integer>(另一种表示)
// 不应该为每种组合写不同的代码!19.6.3 代码示例
基础示例:类型相等包装器
#include <iostream>
#include <type_traits>
// ============================================
// 类型相等包装器原理
// ============================================
// 基础内核(只提供基本类型)
template <typename Kernel_>
struct KernelBase {
// 基础几何对象定义
template <typename K>
struct Point_2_base {
double x_, y_;
Point_2_base(double x = 0, double y = 0) : x_(x), y_(y) {}
double x() const { return x_; }
double y() const { return y_; }
};
template <typename K>
struct Vector_2_base {
double x_, y_;
Vector_2_base(double x = 0, double y = 0) : x_(x), y_(y) {}
double x() const { return x_; }
double y() const { return y_; }
};
};
// 类型相等包装器
// 确保 Kernel::Point_2 和 Point_2<Kernel> 是同一类型
template <typename K_base, typename Kernel_>
struct Type_equality_wrapper : public K_base {
// 通过typedef确保类型相等
typedef typename K_base::template Point_2_base<Kernel_> Point_2;
typedef typename K_base::template Vector_2_base<Kernel_> Vector_2;
};
// 完整的内核定义
template <typename FT = double>
struct MyKernel {
// 首先定义内核类型本身
typedef MyKernel<FT> Self;
// 使用类型相等包装器
typedef Type_equality_wrapper<KernelBase<Self>, Self> Kernel_base;
// 导出几何对象类型
typedef typename Kernel_base::Point_2 Point_2;
typedef typename Kernel_base::Vector_2 Vector_2;
// 数值类型
typedef FT FT_type;
};
// 验证类型相等
int main() {
typedef MyKernel<> Kernel;
// 这两种方式创建的点应该是相同类型
Kernel::Point_2 p1(1.0, 2.0);
std::cout << "Point coordinates: (" << p1.x() << ", " << p1.y() << ")" << std::endl;
// 验证类型相等
static_assert(std::is_same<
Kernel::Point_2,
typename Kernel::Kernel_base::Point_2
>::value, "Types should be equal");
std::cout << "Type equality verified\!" << std::endl;
return 0;
}中级示例:延迟计算基础
#include <iostream>
#include <memory>
#include <math>
// ============================================
// 延迟计算(Lazy Evaluation)基础
// ============================================
// 抽象基类:延迟计算的表示
template <typename ExactType>
struct LazyRep {
mutable bool computed;
mutable ExactType exact_value;
LazyRep() : computed(false) {}
virtual ~LazyRep() = default;
ExactType exact() const {
if (\!computed) {
exact_value = compute_exact();
computed = true;
}
return exact_value;
}
protected:
virtual ExactType compute_exact() const = 0;
};
// 延迟计算的句柄类
template <typename ExactType>
class Lazy {
std::shared_ptr<LazyRep<ExactType>> rep;
public:
Lazy() = default;
explicit Lazy(LazyRep<ExactType>* r) : rep(r) {}
ExactType exact() const {
return rep ? rep->exact() : ExactType();
}
bool is_lazy() const { return rep \!= nullptr; }
};
// 具体实现:常量
template <typename ExactType>
struct LazyConstant : LazyRep<ExactType> {
ExactType value;
explicit LazyConstant(ExactType v) : value(v) {}
protected:
ExactType compute_exact() const override {
return value;
}
};
// 具体实现:加法操作
template <typename ExactType>
struct LazyAdd : LazyRep<ExactType> {
Lazy<ExactType> left, right;
LazyAdd(Lazy<ExactType> l, Lazy<ExactType> r)
: left(std::move(l)), right(std::move(r)) {}
protected:
ExactType compute_exact() const override {
return left.exact() + right.exact();
}
};
// 具体实现:乘法操作
template <typename ExactType>
struct LazyMultiply : LazyRep<ExactType> {
Lazy<ExactType> left, right;
LazyMultiply(Lazy<ExactType> l, Lazy<ExactType> r)
: left(std::move(l)), right(std::move(r)) {}
protected:
ExactType compute_exact() const override {
return left.exact() * right.exact();
}
};
// 延迟计算的数值类型
template <typename ExactType>
class LazyNumber {
Lazy<ExactType> lazy;
double approx; // 近似值用于快速判断
public:
LazyNumber() : approx(0) {}
// 从精确值构造
LazyNumber(ExactType exact, double approx_val)
: lazy(new LazyConstant<ExactType>(exact)), approx(approx_val) {}
// 从近似值构造(延迟精确计算)
explicit LazyNumber(double approx_val) : approx(approx_val) {}
// 从延迟操作构造
LazyNumber(Lazy<ExactType> l, double approx_val)
: lazy(std::move(l)), approx(approx_val) {}
double approximation() const { return approx; }
ExactType exact() const { return lazy.exact(); }
// 加法
LazyNumber operator+(const LazyNumber& other) const {
double new_approx = approx + other.approx;
auto add_rep = new LazyAdd<ExactType>(lazy, other.lazy);
return LazyNumber(Lazy<ExactType>(add_rep), new_approx);
}
// 乘法
LazyNumber operator*(const LazyNumber& other) const {
double new_approx = approx * other.approx;
auto mul_rep = new LazyMultiply<ExactType>(lazy, other.lazy);
return LazyNumber(Lazy<ExactType>(mul_rep), new_approx);
}
};
// 使用示例
int main() {
// 创建延迟计算的数值
LazyNumber<double> a(3.0, 3.0); // 精确值=3.0,近似值=3.0
LazyNumber<double> b(4.0, 4.0);
LazyNumber<double> c(5.0, 5.0);
// 构建表达式树:(a + b) * c
auto sum = a + b; // 不计算,只构建树
auto result = sum * c; // 继续构建树
std::cout << "Approximation: " << result.approximation() << std::endl; // 35.0
std::cout << "Exact: " << result.exact() << std::endl; // 35.0(延迟计算)
return 0;
}高级示例:区间算术
#include <iostream>
#include <math>
#include <algorithm>
#include <iomanip>
// ============================================
// 区间算术实现
// ============================================
// 区间类:表示一个数值范围 [inf, sup]
class Interval {
double inf_, sup_;
public:
// 构造函数
Interval() : inf_(0), sup_(0) {}
Interval(double v) : inf_(v), sup_(v) {}
Interval(double i, double s) : inf_(i), sup_(s) {
if (i > s) std::swap(inf_, sup_);
}
// 访问器
double inf() const { return inf_; }
double sup() const { return sup_; }
double mid() const { return (inf_ + sup_) / 2; }
double width() const { return sup_ - inf_; }
// 判断区间是否包含0
bool contains_zero() const { return inf_ <= 0 && sup_ >= 0; }
// 判断区间是否完全为正/负
bool is_positive() const { return inf_ > 0; }
bool is_negative() const { return sup_ < 0; }
// 判断是否为点区间(精确值已知)
bool is_point() const { return inf_ == sup_; }
// 区间加法(向下/向上舍入)
Interval operator+(const Interval& other) const {
return Interval(
std::nextafter(inf_ + other.inf_, -INFINITY), // 向下舍入
std::nextafter(sup_ + other.sup_, INFINITY) // 向上舍入
);
}
// 区间减法
Interval operator-(const Interval& other) const {
return Interval(
std::nextafter(inf_ - other.sup_, -INFINITY),
std::nextafter(sup_ - other.inf_, INFINITY)
);
}
// 区间乘法(需要考虑所有组合)
Interval operator*(const Interval& other) const {
double a = inf_, b = sup_, c = other.inf_, d = other.sup_;
double products[4] = {a*c, a*d, b*c, b*d};
return Interval(
std::nextafter(*std::min_element(products, products+4), -INFINITY),
std::nextafter(*std::max_element(products, products+4), INFINITY)
);
}
// 区间除法
Interval operator/(const Interval& other) const {
if (other.contains_zero()) {
// 除数包含0,结果为整个实数范围
return Interval(-INFINITY, INFINITY);
}
double a = inf_, b = sup_, c = other.inf_, d = other.sup_;
double quotients[4] = {a/c, a/d, b/c, b/d};
return Interval(
std::nextafter(*std::min_element(quotients, quotients+4), -INFINITY),
std::nextafter(*std::max_element(quotients, quotients+4), INFINITY)
);
}
// 输出
friend std::ostream& operator<<(std::ostream& os, const Interval& i) {
os << "[" << std::setprecision(6) << i.inf_ << ", " << i.sup_ << "]";
return os;
}
};
// ============================================
// 使用区间算术进行鲁棒几何计算
// ============================================
struct Point {
Interval x, y;
Point(const Interval& x_, const Interval& y_) : x(x_), y(y_) {}
Point(double x_, double y_) : x(x_), y(y_) {}
};
// 使用区间算术计算方向
// 返回值:
// 正区间:确定左转
// 负区间:确定右转
// 包含0的区间:不确定,需要精确计算
Interval orientation_interval(const Point& p, const Point& q, const Point& r) {
// 计算 (q-p) x (r-p)
Interval qpx = q.x - p.x;
Interval qpy = q.y - p.y;
Interval rpx = r.x - p.x;
Interval rpy = r.y - p.y;
return qpx * rpy - qpy * rpx;
}
// 鲁棒的方向判断
enum Orientation {
LEFT_TURN,
RIGHT_TURN,
COLLINEAR,
UNCERTAIN // 区间算术无法确定
};
Orientation robust_orientation(const Point& p, const Point& q, const Point& r) {
Interval orient = orientation_interval(p, q, r);
if (orient.is_positive()) return LEFT_TURN;
if (orient.is_negative()) return RIGHT_TURN;
if (orient.contains_zero()) {
if (orient.is_point()) return COLLINEAR;
return UNCERTAIN; // 需要精确计算
}
return UNCERTAIN;
}
// ============================================
// 演示
// ============================================
int main() {
std::cout << std::setprecision(10);
// 基本区间运算
Interval a(1.0, 2.0);
Interval b(3.0, 4.0);
std::cout << "Interval arithmetic demo:" << std::endl;
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
std::cout << "a + b = " << (a + b) << std::endl;
std::cout << "a * b = " << (a * b) << std::endl;
// 几何方向判断
std::cout << "\nOrientation tests:" << std::endl;
// 明显的左转
Point p1(0, 0), q1(1, 0), r1(0.5, 1);
Interval orient1 = orientation_interval(p1, q1, r1);
std::cout << "Left turn orientation: " << orient1 << std::endl;
std::cout << "Result: " << robust_orientation(p1, q1, r1) << std::endl;
// 接近共线的情况(可能不确定)
Point p2(0, 0), q2(1, 0), r2(2, 0.0000001);
Interval orient2 = orientation_interval(p2, q2, r2);
std::cout << "\nNear-collinear orientation: " << orient2 << std::endl;
std::cout << "Contains zero: " << orient2.contains_zero() << std::endl;
return 0;
}19.6.4 CGAL中的应用
CGAL的Type_equality_wrapper
// 来自 CGAL/Kernel/Type_equality_wrapper.h
namespace CGAL {
template < typename K_base, typename Kernel_ >
struct Type_equality_wrapper : public K_base {
typedef K_base Kernel_base;
// 使用宏批量定义类型相等
#define CGAL_Kernel_obj(X) typedef CGAL::X<Kernel_> X;
#include <CGAL/Kernel/interface_macros.h>
// 额外的类型定义
typedef CGAL::Conic_2<Kernel_> Conic_2;
typedef CGAL::Aff_transformation_2<Kernel_> Aff_transformation_2;
};
} // namespace CGALCGAL的Lazy_exact_nt
// 来自 CGAL/Lazy_exact_nt.h(简化展示)
namespace CGAL {
template <class ET> // ET = Exact Type
class Lazy_exact_nt : public Lazy<Interval_nt<false>, ET, To_interval<ET> > {
public:
typedef ET ET_type;
typedef Interval_nt<false> AT_type; // Approximation Type
// 构造:从近似值开始,延迟精确计算
Lazy_exact_nt(double d) : base_type(new Lazy_exact_Cst<ET>(d)) {}
// 构造:从精确值
Lazy_exact_nt(const ET& e) : base_type(new Lazy_exact_Ex_Cst<ET>(e)) {}
// 操作符:构建延迟计算图
Lazy_exact_nt operator+(const Lazy_exact_nt& other) const {
return Lazy_exact_nt(
new Lazy_exact_Add<ET>(*this, other),
this->approx() + other.approx() // 同时计算近似值
);
}
// 获取精确值(触发计算)
const ET& exact() const {
if (this->is_lazy()) {
this->update_exact(); // 计算并缓存
}
return *this->ptr();
}
// 获取近似值(快速)
const AT_type& approx() const {
return this->approx_; // 直接返回,不计算
}
};
} // namespace CGALCGAL的Interval_nt
// 来自 CGAL/Interval_nt.h(简化展示)
namespace CGAL {
template <bool Protected = true>
class Interval_nt {
double inf_, sup_;
public:
// 保护舍入模式的构造
Interval_nt(double i, double s) {
if (Protected) {
// 设置舍入模式
int old_mode = CGAL::FPU_get_and_set_cw(CGAL_FE_TONEAREST);
inf_ = i;
sup_ = s;
CGAL::FPU_set_cw(old_mode);
} else {
inf_ = i;
sup_ = s;
}
}
// 安全的算术运算
Interval_nt operator+(const Interval_nt& other) const {
Protector P; // RAII保护舍入模式
return Interval_nt(
CGAL_IA_FORCE_TO_DOUBLE(inf_ + other.inf_), // 向下舍入
CGAL_IA_FORCE_TO_DOUBLE(sup_ + other.sup_) // 向上舍入
);
}
// 判断区间关系
bool is_point() const { return inf_ == sup_; }
bool is_same(const Interval_nt& other) const {
return inf_ == other.inf_ && sup_ == other.sup_;
}
// 与0比较
bool is_positive() const { return inf_ > 0; }
bool is_negative() const { return sup_ < 0; }
bool is_zero() const { return inf_ <= 0 && sup_ >= 0; }
Sign sign() const {
if (is_positive()) return POSITIVE;
if (is_negative()) return NEGATIVE;
return ZERO; // 实际上可能是不确定的
}
};
} // namespace CGAL19.6.5 常见陷阱
陷阱1:舍入模式未正确设置
// 错误:未保护舍入模式
Interval_nt add_unsafe(const Interval_nt& a, const Interval_nt& b) {
// 舍入模式不确定,结果可能错误
return Interval_nt(a.inf() + b.inf(), a.sup() + b.sup());
}
// 正确:使用RAII保护舍入模式
Interval_nt add_safe(const Interval_nt& a, const Interval_nt& b) {
Interval_nt::Protector P; // 设置并恢复舍入模式
return Interval_nt(
CGAL_IA_FORCE_TO_DOUBLE(a.inf() + b.inf()),
CGAL_IA_FORCE_TO_DOUBLE(a.sup() + b.sup())
);
}陷阱2:延迟计算的内存管理
// 危险:内存泄漏
template <typename ET>
Lazy_exact_nt<ET> operator+(const Lazy_exact_nt<ET>& a,
const Lazy_exact_nt<ET>& b) {
// 每次加法都分配新内存,没有释放机制
return Lazy_exact_nt<ET>(new Lazy_exact_Add<ET>(a, b));
}
// 正确:使用智能指针管理
template <typename ET>
class Lazy_exact_nt {
std::shared_ptr<Lazy_exact_nt_rep<ET>> rep;
// ...
};陷阱3:区间算术的过度保守
// 问题:区间算术可能过于保守
Interval a(1.0, 2.0);
Interval b(3.0, 4.0);
Interval c = a - a; // 结果是 [-1, 1],不是 [0, 0]!
// 解决方案:识别并简化相同表达式
// 在CGAL中,这需要表达式模板或符号分析19.6.6 最佳实践
实践1:内核选择策略
// 根据需求选择合适的内核
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
// 快速但不精确(适用于大多数情况)
typedef CGAL::Exact_predicates_inexact_constructions_kernel K_fast;
// 完全精确(需要精确构造时)
typedef CGAL::Exact_predicates_exact_constructions_kernel K_exact;
// 自定义内核(特殊需求)
typedef CGAL::Cartesian<CGAL::Lazy_exact_nt<CGAL::Gmpq>>> K_lazy;实践2:延迟计算的显式转换
// 明确何时需要精确值
Lazy_exact_nt<Gmpq> lazy_value = /* ... */;
// 使用近似值进行快速过滤
if (lazy_value.approx().is_positive()) {
// 确定为正,无需精确计算
}
// 必要时才获取精确值
if (lazy_value.approx().contains_zero()) {
// 不确定,需要精确值
Gmpq exact = lazy_value.exact();
// ...
}实践3:区间算术的过滤策略
// 多级过滤策略
template <typename Kernel>
Sign orientation(const Point& p, const Point& q, const Point& r) {
// 第1级:区间算术快速判断
Interval_nt<> orient_interval = compute_interval(p, q, r);
if (orient_interval.is_positive()) return POSITIVE;
if (orient_interval.is_negative()) return NEGATIVE;
// 第2级:精确计算
return compute_exact(p, q, r);
}本节要点
-
类型相等包装器解决循环依赖:通过模板参数传递内核类型,确保
Kernel::Point_2和Point_2<Kernel>是同一类型。 -
延迟计算平衡性能与精度:只在需要时进行精确计算,大部分时间使用快速近似值。
-
区间算术提供鲁棒性保证:通过维护数值范围,可以检测和避免浮点误差导致的几何错误。
-
过滤策略优化性能:先使用快速方法判断,只在必要时进行精确计算。
-
舍入模式管理至关重要:区间算术需要精确控制浮点舍入方向。
-
内核选择影响整体性能:根据应用需求选择合适的内核类型。
进一步阅读
- CGAL文档:Kernel Manual中的”Kernel Design”章节
- 《The Design and Implementation of CGAL》:CGAL设计的原始论文
- 《Robust Geometric Computing》:鲁棒几何计算的深入讨论