2023年6月9日 星期五

C++:簡介 Type Erasure

本文為 C++ Software Design 書的第 32 節內容。

Type Erasure 為前面幾節中介紹模式的集大成。Type Erasure 的設計是為了達到以下目的:
  • Value-based 讓 copy、move 與變數的型態更直覺。
  • Nonintrusive 就不需要改變任何已存在的類別。
  • Extendable 容易擴展至不同的類別。
  • Potentially nonpolymorphic 減少不同類別間的多態要求。
  • Same semantic behavior 就抽象上來說達成一樣的功能。
以下的例子與前文非常類似,主要差別是 ShapeModel 現在儲存了一個具體的類別,因此改稱為 OwningShapeModel。

// Non-polymorphic types
class Circle {
  explicit Circle(double radius) { /*...*/ }
};
class Square {
  explicit Square(double side) { /*...*/ }
};

namespace detail {
class ShapeConcept {
  virtual ~ShapeConcept() = default;
  virtual void draw() const = 0;
  virtual std::unique_ptr<ShapeConcept> clone() const = 0;
};

template <typename ShapeT, typename DrawStrategy>
class OwningShapeModel : public ShapeConcept {
public:
  explicit OwningShapeModel(ShapeT shape, DrawStrategy drawer) 
    : shape_{std::move(shape)}
    , drawer_{std::move(drawer)} {}
  
  void draw() const override {drawer_(shape_);}
  std::unique_ptr<ShapeConcept> clone() override {
    return std::make_unique<OwningShapeModel>(*this);
  }
private:
  ShapeT shape_;
  DrawStrategy drawer_;
};
} // namespace detail

class Shape {
public:
  template <typename ShapeT, typename DrawStrategy>
  Shape(ShapeT shape, DrawStrategy drawer) {
    using Model = detail::OwningShapeModel<ShapeT, DrawStrategy>;
    pimpl_ = std::make_unique<Model>(std::move(shape), 
                                     std::move(drawer));
  }
  
  Shape(Shape const& other) : pimpl_(other.pimpl_->clone()) {}
  Shape& operator=(Shape const& other) {
    // Copy-and-swap idiom
    Shape copy(other);
    pimpl_.swap(copy.pimpl_);
    return *this;
  }
  
  ~Shape() = default;
  Shape(Shape&&) = default;
  Shape& operator=(Shape&&) = default;

private:
  friend void draw(Shape const& shape) {
    shape.pimpl_->draw();
  }
  std::unique_ptr<detail::ShapeConcept> pimpl_;
};
上面這段 code 藏了非常多細節,以下一一解釋:
  • Circle 與 Square 為 non-polymorphic 的類別,沒有任何繼承的關係。
  • ShapeConcept 與 OwningShapeModel 被包在 detail 裡面避免讓外面直接呼叫。
  • OwningShapeModel 在 Shape 裡面為 pimpl,也就是說 Shape 並沒有任何真正型態的資訊,都存在 OwningShapeModel 之中。也因為這樣讓此技巧被稱為 Type Erasure。
  • 必須實作 rules of five,其中 copy constructor 用 copy-and-swap idiom 實作。
  • Shape 中的 draw 函數使用 hidden friend 技巧,這樣就能有 free function 的 draw 函數。

以下為使用 Shape 的例子:

int main() {
  Circle circle{3.14};
  auto drawer = [](Circle const& c) { /**/ };
  
  Shape shape1(circle, drawer);
  Shape shape2(shape1); // Copy constructor
  draw(shape2); // Hidden friend draw function
  
  return 0;
}
Type Erasure 的主要缺點為有太多實作細節需要注意;另一個缺點是 binary operation(像是 ==)不容易實作。而從效能的角度上看,雖然 Type Erasure 與 OO 設計的類別效能差不多,但其擁有 non-polymorphism 的特性使得實作上有更多彈性。我們之後的文章也會介紹如何優化 Type Erasure。

沒有留言:

張貼留言