讀《An Adaptable and Extensible Geometry Kernel》

讀《An Adaptable and Extensible Geometry Kernel》

利用Curiously Recurring Template Pattern替代虛函數

詳細內容能夠參考[1]。這裏單純列舉出相關的代碼示例:html

// 使用繼承的方式實現不一樣圖形的繪製
class Shape
{
public:
    Shape() {}
    virtual ~Shape() {}
    virtual void Draw() = 0;
};

class Triangle : public Shape
{
public:
    Triangle() {}
    ~Triangle() {}

    void Draw() { cout << "Draw a Triangle" << endl; }
};

class Rectangle : public Shape
{
public:
    Rectangle() {}
    ~Rectangle() {}

    void Draw() { cout << "Draw a Rectangle" << endl; }
};

// 利用Curiously Recurring Template Pattern
template <typename Derived>
class Shape
{
public:
    void Draw()
    {
        return static_cast<Derived*>(this)->Draw();
    }
};
 
class Triangle : public Shape<Triangle>
{
public:
    void Draw() { cout << "Draw a Triangle" << endl; }
};
 
class Rectangle : public Shape<Rectangle>
{
public:
    void Draw() { cout << "Draw a Rectangle" << endl; }
};

爲何須要Kernel

經過Kernel須要解決的主要問題是代碼的適配性和可擴展性。那爲何能夠提升適配性和可擴展性能夠在後續的內容中獲得答案。算法

Kernel的概念和架構

常見的數據結構和算法的設計,數據結構爲獨立的類,算法爲全局或類的成員函數。示例以下:數據結構

K::Point_2   p(0,1), q(1,-4);   // 數據結構
K::Line_2    line(p,q);

if (less_xy_2(p, q)) { ... }    // 算法成員函數

幾何Kernel包含須要操做的類型,以及針對這些類型的操做。Kernel會將上述相關的內容進行打包處理。示例以下:架構

K k;
K::Construct_line_2  c_line = k.construct_line_2_object();
K::Less_xy_2         less_xy = k.less_xy_2_object();
K::Point_2           p(0,1), q(1,-4);
K::Line_2            line = c_line(p, q);

if (less_xy(p, q)) { ... }

Kernel將數據結構和算法相關的細節放到了內部。總體的架構能夠分爲三層,Kernel, Geometric Primitives,Numeric Primitives,具體以下:less

Kernel的實現

初版本

template <class K> struct MyPoint { };
template <class K> struct MyLine { };
template <class K> struct MyConstruct { };
template <class K> struct MyLess { };

struct Kernel {
    typedef MyPoint<Kernel> Point_2;
    typedef MyLine<Kernel> Line_2;
    typedef MyConstruct<Kernel> Construct_line_2;
    typedef MyLess<Kernel> Less_xy_2;
};

// Generate new Kernel
template <class K> struct NewPoint { };
template <class K> struct MyLeftTurn { };
struct New_kernel : public Kernel {
    typedef NewPoint<New_kernel> Point_2;
    typedef MyLeftTurn<New_kernel> Left_turn_2;
};

int main()
{
    New_kernel::Point_2 p, q;
    New_kernel::Construct_line_2 construct_line_2;
    New_kernel::Line_2 l = construct_line_2(p, q);
    return 0;
}

測試環境能夠見: https://ideone.com/MrXCDD數據結構和算法

編譯錯誤爲:ide

prog.cpp: In function ‘int main()’:
prog.cpp:28:49: error: no match for call to ‘(Kernel::Construct_line_2 {aka MyConstruct<Kernel>}) (New_kernel::Point_2&, New_kernel::Point_2&)’
     New_kernel::Line_2 l = construct_line_2(p, q);

從編譯錯誤中可見,New_kernel::Construct_line_2其實調用的是MyConstruct<Kernel>的實現,而咱們想要的調用是MyConstruct<New_kernel>。依賴關係見下圖:函數

這個版本中另外一個隱含的問題是,循環引用的問題,具體以下:post

template <class K> struct P {
    typedef K::A B;
};

struct Kernel {
    typedef P<Kernel>::B B;
    typedef int A;
};

爲了解決上面的問題,進行了第二版本的改進。測試

第二版本

爲了下降不一樣Kernel之間的關聯性,引入Kernel_base,具體以下:

template <class K> struct MyPoint { };
template <class K> struct MyLine { };
template <class K> struct MyConstruct { };
template <class K> struct MyLess { };

template <class K>
struct Kernel_base {
    typedef MyPoint<K> Point_2;
    typedef MyLine<K> Line_2;
    typedef MyConstruct<K> Construct_line_2;
    typedef MyLess<K> Less_xy_2;
};

struct Kernel : public Kernel_base<Kernel> { };

// Generate new Kernel
template <class K> struct NewPoint { };
template <class K> struct MyLeftTurn { };

template<class K>
struct New_kernel_base : public Kernel_base<K> {
    typedef NewPoint<K> Point_2;
    typedef MyLeftTurn<K> Left_turn_2;
};

struct New_kernel : public New_kernel_base<New_kernel> {};

int main()
{
    New_kernel::Point_2 p, q;
    New_kernel::Construct_line_2 construct_line_2;
    New_kernel::Line_2 l = construct_line_2(p, q);
    return 0;
}

測試環境能夠見:https://ideone.com/40wOCa

編譯錯誤以下:

prog.cpp: In function ‘int main()’:
prog.cpp:35:49: error: no match for call to ‘(Kernel_base<New_kernel>::Construct_line_2 {aka MyConstruct<New_kernel>}) (New_kernel_base<New_kernel>::Point_2&, New_kernel_base<New_kernel>::Point_2&)’
     New_kernel::Line_2 l = construct_line_2(p, q);
                                                 ^

從編譯結果中可得,Construct_line_2對應的New_kernel正是咱們所預期的。接下來須要解決的問題是,construct_line_2並非能夠調用的函數。調整後kernel之間的依賴關係以下:

第三版本

該版本中,利用函數對象來處理操做邏輯。

template <class K> struct MyPoint { };
template <class K> struct MyLine { };
template <class K> struct MyConstruct { 
    typedef typename K::Line_2 Line_2;
    typedef typename K::Point_2 Point_2;

    Line_2 operator() (Point_2, Point_2) const
    {
        return Line_2();
    }
};

template <class K> struct MyLess {
    typedef typename K::Point_2 Point_2;

    bool operator() (Point_2, Point_2) const
    {
        return true;
    }
};

template <class K>
struct Kernel_base {
    typedef MyPoint<K> Point_2;
    typedef MyLine<K> Line_2;
    typedef MyConstruct<K> Construct_line_2;
    typedef MyLess<K> Less_xy_2;
    Construct_line_2 construct_line_2_object();
    Less_xy_2        less_xy_2_object();
};

struct Kernel : public Kernel_base<Kernel> { };

// Generate new Kernel
template <class K> struct NewPoint { };
template <class K> struct MyLeftTurn { };

template<class K>
struct New_kernel_base : public Kernel_base<K> {
    typedef NewPoint<K> Point_2;
    typedef MyLeftTurn<K> Left_turn_2;
};

struct New_kernel : public New_kernel_base<New_kernel> {};

int main()
{
    New_kernel::Point_2 p, q;
    New_kernel::Construct_line_2 construct_line_2;
    New_kernel::Line_2 l = construct_line_2(p, q);
    return 0;
}

示例程序見:https://ideone.com/6ISelp

整個編譯過程成功經過。

到此處,整個kernel的結構基本完善了。

Kernel使用示例說明算法的適應性

以2D點集凸包計算的實現來舉例:https://doc.cgal.org/latest/Convex_hull_2/index.html。僅僅針對算法實現過程當中Kernel的使用進行簡單說明,對算法的具體實現此處不進行介紹

// 暴露給外部調用的接口
template <class InputIterator, class OutputIterator>
inline
OutputIterator
ch_graham_andrew( InputIterator first,
                  InputIterator last,
                  OutputIterator result)
{
    typedef std::iterator_traits<InputIterator> ITraits;
    typedef typename ITraits::value_type        value_type;
    typedef CGAL::Kernel_traits<value_type>     KTraits;    // 根據value_type獲取KernelTraits
    typedef typename KTraits::Kernel            Kernel;     // 進一步獲取Kernel
    return ch_graham_andrew(first, last, result, Kernel()); // 傳入Kernel,調用具體實現
}

// 具體實現
template <class InputIterator, class OutputIterator, class Traits>
OutputIterator
ch_graham_andrew( InputIterator  first,
                       InputIterator  last,
                       OutputIterator result,
                       const Traits&  ch_traits)
{
  typedef  typename Traits::Point_2     Point_2;     // 獲取Kernel中的類型
  typedef  typename Traits::Equal_2      Equal_2;    // 獲取Kernel中的類型
  
  Equal_2      equal_points = ch_traits.equal_2_object();  // 獲取kernel中的算法

  if (first == last) return result;
  std::vector< Point_2 >  V (first, last);
  std::sort( V.begin(), V.end(), ch_traits.less_xy_2_object() ); // 獲取Kernel中的算法
  if (equal_points( *(V.begin()), *(V.rbegin())) )
  {
      *result++ = *(V.begin());
      return result;
  }

  #if defined(CGAL_CH_NO_POSTCONDITIONS) || defined(CGAL_NO_POSTCONDITIONS) \
    || defined(NDEBUG)
  OutputIterator  res(result);
  #else
  Tee_for_output_iterator<OutputIterator,Point_2> res(result);
  #endif // no postconditions ...
  ch__ref_graham_andrew_scan( V.begin(), V.end(),  res, ch_traits);
  ch__ref_graham_andrew_scan( V.rbegin(), V.rend(), res, ch_traits);
  CGAL_ch_postcondition( \
      is_ccw_strongly_convex_2( res.output_so_far_begin(), \
                                     res.output_so_far_end(), \
                                     ch_traits));
  CGAL_ch_expensive_postcondition( \
      ch_brute_force_check_2( \
          V.begin(), V.end(), \
          res.output_so_far_begin(), res.output_so_far_end(), \
          ch_traits));
  #if defined(CGAL_CH_NO_POSTCONDITIONS) || defined(CGAL_NO_POSTCONDITIONS) \
    || defined(NDEBUG)
  return res;
  #else
  return res.to_output_iterator();
  #endif // no postconditions ...
}

從上面簡單的示例可得,通常在算法構建的時候會在最外層生成調用接口,而後,在具體實現中,經過分別對Kernel中的數據結構和算法的調用,最後組裝成一個完整的算法實現。

簡單的完整的Kernel

此處將文章最後的示例代碼貼出來,用於進一步完善對Kernel的認知。

//------------------------------------------------------------
// bottom layer: number type based function toolbox
//

template <class FT>
FT determinant2x2(FT a00, FT a01, FT a10, FT a11)
{
    return a00*a11 - a10*a01;
}

template <class FT>
void line_from_pointsC2(FT px, FT py, FT qx, FT qy, FT &a, FT &b, FT &c) {}

//------------------------------------------------------------
// mid layer: representations, predicates and constructions
//

template <class K_>
struct Point_2 {
    typedef K_ K;
    typedef typename K::FT FT;
    Point_2() {}
    Point_2(FT x_, FT y_) : x(x_), y(y_) {}
    FT x, y;
};

template <class K_>
struct Line_2 {
    typedef K_ K;
    typedef typename K::Point_2 Point_2;
    Line_2() {}
    Line_2(Point_2 p, Point_2 q) { *this = K::Construct_line_2(p,q); }
    typename K::FT a, b, c;
};

template <class K_>
struct Segment_2 {
    typedef K_ K;
    typename K::Point_2 s, e;
};

template <class K_>
struct Less_xy_2 {
    typedef typename K_::Point_2 Point_2;
    bool operator()(Point_2 p, Point_2 q) const
    { return p.x < q.x || p.x == q.x && p.y < q.y; }
};

template <class K_>
struct Left_turn_2 {
    typedef typename K_::Point_2 Point_2;
    bool operator()(Point_2 p, Point_2 q, Point_2 r) const
    {
        return determinant2x2(q.x - p.x, q.y - p.y,
                              r.x - p.x, r.y - q.y) > 0;
    }
};

template <class K_>
struct Construct_line_2 {
    typedef typename K_::Point_2 Point_2;
    typedef typename K_::Line_2 Line_2;
    Line_2 operator()(Point_2 p, Point_2 q) const {
        Line_2 l;
        Line_from_pointsC2(p.x, p.y, q.x, q.y, l.a, l.b, l.c);
        return l;
    }
};

//------------------------------------------------------------
// top layer: geometric kernel
//

template <class K_, class FT_>
struct Kernel_bae {
    typedef K_                           K;
    typedef FT_                          FT;
    typedef Point_2<K>                   Point_2;
    typedef Line_2<K>                    Line_2;
    typedef Segment_2<K>                 Segment_2;
    typedef Less_xy_2<K>                 Less_xy_2;
    typedef Left_turn_2<K>               Left_turn_2;
    typedef Construct_line_2<K>          Construct_line_2;

    Less_xy_2 less_xy_2_object() const { return Less_xy_2(); }
    Left_turn_2 Left_turn_2_object() const { return Left_turn_2(); }
    Construct_line_2 construct_line_2_object() const { return Construct_line_2(); }
};

template <class FT_>
struct Kernel : public Kernel_base<Kernel<FT_>, FT_>
{};

//------------------------------------------------------------
// convenience layer: global functions
//

template < class K >inline
bool
less_xy_2(typename K::Point_2 p,typename K::Point_2 q, K k = K())
{ returnk.less_xy_2_object()(p, q); }

template < class K >inline
bool
left_turn_2(typenameK::Point_2 p,
            typenameK::Point_2 q,
            typenameK::Point_2 r,
            K k = K())
{ returnk.left_turn_2_object()(p, q, r); }

//------------------------------------------------------------
// enve more convenience: specializations for kernel
//

template < class FT > inline
bool
left_turn_2(Point_2< Kernel< FT > > p,
            Point_2< Kernel< FT > > q,
            Point_2< Kernel< FT > > r)
{ returnleft_turn_2(p, q, r, Kernel< FT >()); }

template < class FT >inline
bool
less_xy_2(Point_2< Kernel< FT > > p, Point_2< Kernel< FT > > q)
{ returnless_xy_2(p, q, Kernel< FT >()); }

參考

相關文章
相關標籤/搜索