type erasure

type erasure相比于经典的基于继承实现的运行时多态,有一大优点,接口类和实现类不再需要是基类和派生类的关系。 Type erasure有两个特点

  • 构造函数是模板
  • 完全非虚函数接口

我们简要介绍下如何实现type erasure。

值语义的type erasure

以经典的Shape和派生类Circle Square为例说明

#include <memory>
#include <iostream>

using namespace std;

class Base
{
 public:
  virtual void draw()const = 0;
  virtual ~Base() {}

};

template<class T>
class Derive : public Base
{
  T t_;

 public:
  Derive(T ta) : t_(std::move(ta)) {}
  void draw() const override { t_.draw(); }
};
class Shape
{
  std::unique_ptr<Base> pbase;
 public:
  template<class T>
  Shape(T t) : pbase(std::make_unique<Derive<T>>(std::move(t)))
  {
  }
  void draw() const { pbase->draw();
  }
};
class Circle
{
 public:
  void draw() const { cout << "draw Circle"; }
};
class Square
{
  void draw() const { cout << "draw Square"; }
};

int main()
{
  Circle c;
  Shape  s(std::move(c));
  s.draw();
  return 0;
}

可以看到使用type erasure实现的运行时多态,Shape和Circle不再是基类和派生类的关系。多态具体实现如Circle类只需实现非虚成员函数draw就可,不需要继承自Shape。而Shape的构造函数是模板。Shape有一成员pbase,pbase由Derive<T>类型的对象指针初始化,而Derive<T>有一成员t_用于存储Shape的构造函数传入的参数t。Shape的draw函数的实现调用pbase的draw函数,而pbase的draw虚函数将调用派生类Derive<T>的draw函数,而Derive<T>的draw函数将调用成员t_的draw函数,而t_移动构造自形式参数t,也就是相当于调用t的draw函数。例如上述main中的Shape类型s的构造函数传入了Circle类型的c,那么s.draw()会调用Circle类型draw函数。

reference语义的type erasure

上文的Derive保存一个T类型的对象t_,也就是说需要保存一个副本,相当于值语义,如果需要实现引用语义,那么Derive将保存T类型的指针而不是对象,ShapeRef的构造函数参数类型是左值引用T&而不再是T:

#include <memory>
#include <iostream>

using namespace std;

class Base
{
 public:
  virtual void draw()const = 0;
  virtual ~Base() {}

};

template<class T>
class Derive : public Base
{
  T* t;

 public:
  Derive(T* ta) : t(ta) {}
  void draw() const override { t->draw(); }
};

class ShapeRef
{
  std::unique_ptr<Base> pbase;

 public:
  template<class T>
  ShapeRef(T& t) : pbase(std::make_unique<Derive<T>>(&t))
  {
  }
  void draw() const { pbase->draw();
  }
};

class Circle
{
 public:
  void draw() const { cout << "draw Circle"; }
};
class Square
{
  void draw() const { cout << "draw Square"; }
};

int main()
{
  Circle c;
  ShapeRef  s(c);
  s.draw();
  return 0;
}
Posted 2023-05-01