肖恩·普恩特(Sean Parent)的演讲Inheritance is the base class of evil表示,多态性不是类型的属性,而是其使用方式的属性。根据经验,请勿使用继承来实现接口(interface)。这样做的众多好处之一就是对具有虚函数的类进行非虚拟化,这仅是因为它们正在实现接口(interface)。这是一个例子:

class Drawable
{
public:
virtual void draw() = 0;
};

class DrawA : public Drawable
{
public:
void draw() override{//do something}
};

class UseDrawable
{
public:
void do(){mDraw->draw();}
Drawable* mDraw;
};

在这里,您可以让它使用类型擦除的类,而不是需要将UseDrawable用作mDrawDrawable*,它可以包装任何实现称为draw的成员的类。因此,类似于带有适当定义的boost::type_erasure::any。这样,DrawA不需要从Drawable继承-多态性实际上是UseDrawable的要求,而不是DrawA的属性。

我正在尝试遵循此原理重构一些代码。我有一个抽象类ModelInterface和两个具体的类ModelAModelB继承自ModelInterface。按照Sean的建议,不要将ModelAModelB强制进入继承层次结构,而仅在需要一个满足ModelInterface建模概念的类的位置上使用类型擦除,这是有道理的。

现在,我的问题是我代码中当前使用ModelInterface的大多数位置也通过基于运行时配置文件构造适当的对象来这样做。当前,工厂将new一个适当的对象并返回ModelInterface*。如果我在代码的这些位置重构代码以使用类型擦除的概念(例如boost::type_erasure::any<implement ModelInterface>之类的代码),那么如何在运行时构造此类对象? ModelAModelB是否仍需要是启用RTTI的类?还是我可以在没有RTTI信息的情况下进行工厂构造和使用?

(使用RTTI,我可以有一个抽象类,例如FactoryConstructible,并使用dynamic_cast<void*>获取最终类型。)

最佳答案

类型擦除101:

步骤1:制作隐藏细节的常规(或半常规仅移动)类型。

struct exposed_type;

此类公开了您要支持的概念。复制,移动,销毁,等于,总订单,哈希和/或您需要支持的任何自定义概念。
struct exposed_type {
  exposed_type(exposed_type const&);
  exposed_type(exposed_type&&);
  friend bool operator<(exposed_type const&, exposed_type const&);
  friend std::size_t hash(exposed_type const&);
  // etc
};

在当前的基于继承的解决方案中,可以从纯虚拟接口(interface)方法粗略地映射许多这些概念。

在您的Regular类型中创建表示概念的非虚拟方法。复制/分配以进行复制等

第2步:编写类型擦除助手。
struct internal_interface;

在这里,您具有纯虚拟接口(interface)。复制的clone()
struct internal_interface {
  virtual ~internal_interface() {}
  virtual internal_interface* clone() const = 0;
  virtual int cmp( internal_interface const& o ) const = 0;
  virtual std::size_t get_hash() const = 0;
  // etc
  virtual std::type_info const* my_type_info() const = 0;
};

在上面的“常规”类型中为此存储一个智能指针1。
struct exposed_type {
  std::unique_ptr<internal_interface> upImpl;

将常规方法转发给助手。例如:
exposed_type::exposed_type( exposed_type const& o ):
  upImpl( o.upImpl?o.upImpl->clone():nullptr )
{}
exposed_type::exposed_type( exposed_type&& o )=default;

第3步:编写类型擦除实现。这是一个template类,用于存储T并从帮助程序继承,并将接口(interface)转发至T。使用免费函数(类似于std::begin),如果未找到adl免费函数,则使用默认实现中的方法。
// used if ADL does not find a hash:
template<class T>
std::size_t hash( T const& t ) {
  return std::hash<T>{}(t);
}
template<class T>
struct internal_impl:internal_interface {
  T t;
  virtual ~internal_impl() {}
  virtual internal_impl* clone() const {
    return new internal_impl{t};
  }
  virtual int cmp( internal_interface const& o ) const {
    if (auto* po = dynamic_cast<internal_interface const*>(&o))
    {
      if (t < *po) return -1;
      if (*po < t) return 1;
      return 0;
    }
    if (my_type_info()->before(*o.my_type_info()) return -1;
    if (o.my_type_info()->before(*my_type_info()) return 1;
    ASSERT(FALSE);
    return 0;
  }
  virtual std::size_t get_hash() const {
    return hash(t);
  }
  // etc
  std::type_info const* my_type_info() const {
    return std::addressof( typeid(T) ); // note, static type, not dynamic
  }
};

步骤4:向您的常规类型添加一个构造函数,该结构接受一个T并从中构造一个类型擦除实现,并将其填充到指向助手的智能指针中。
template<class T,
  // SFINAE block using this ctor as a copy/move ctor:
  std::enable_if_t<!std::is_same<exposed_type, std::decay_t<T>>::value, int>* =nullptr
>
exposed_type( T&& t ):
  upImpl( new internal_impl<std::decay_t<T>>{std::forward<T>(t)} )
{}

完成所有这些工作之后,您现在拥有了具有常规(或半常规)值类型的非侵入式多态系统。

您的工厂函数返回常规类型。

查看std::function的示例实现以完全完成此操作。

根据您是否要在写数据上存储不可变/副本还是手动克隆,唯一和共享都是不错的选择。

关于c++ - 澄清肖恩·帕特恩(Sean Parent)的讲话 “Inheritance is the base class of evil”,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26199126/

10-09 17:02