本文為 C++ Software Design 書的第 32 節內容。
- Value-based 讓 copy、move 與變數的型態更直覺。
- Nonintrusive 就不需要改變任何已存在的類別。
- Extendable 容易擴展至不同的類別。
- Potentially nonpolymorphic 減少不同類別間的多態要求。
- Same semantic behavior 就抽象上來說達成一樣的功能。
// 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。
沒有留言:
張貼留言