澄清肖恩父母的讲话“继承是邪恶的基础”

时间:2014-10-05 02:39:57

标签: c++ inheritance c++11 factory type-erasure

Sean Parent的演讲Inheritance is the base class of evil说,多态性不是该类型的属性,而是它的使用方式。作为一个拇指规则,不要使用继承来实现接口。这样做的许多好处之一是具有虚函数的类的虚拟化只是因为它们实现了一个接口。这是一个例子:

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要求mDraw成为Drawable*,而是可以包含任何实现名为{{1}的成员的类}}。所以,像具有适当定义的draw。这样,boost::type_erasure::any不需要从DrawA继承 - 多态性确实是Drawable的要求,而不是UseDrawable的属性。

我正在尝试按照这个原则重构一些代码。我有一个抽象类DrawA和两个具体的类ModelInterfaceModelA继承自ModelB。根据Sean的建议,不要强制ModelInterfaceModelA进入继承层次结构,只需在需要满足由{{1}建模的概念的类的位置使用类型擦除}。

现在,我的问题是我的代码中当前使用ModelB的大多数地方也是通过基于运行时配置文件构造适当的对象来实现的。目前,工厂将ModelInterface一个合适的对象并返回ModelInterface。如果我重构代码以在代码中的这些位置使用类型擦除的概念(例如new之类的东西),我如何在运行时构造这样的对象? ModelInterface*boost::type_erasure::any<implement ModelInterface>仍需要启用RTTI的课程吗?或者我可以在没有RTTI信息的情况下工厂构建和使用它们吗?

(使用RTTI,我可以有一个抽象类,比如ModelA,并使用ModelB来获取最终类型。)

1 个答案:

答案 0 :(得分:17)

类型擦除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
};

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

在Regular类型中创建表达概念的非虚方法。复制/分配复制等

步骤2:编写类型擦除助手。

struct internal_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并继承自助手,并将接口转发给T。如果没有找到无adl自由函数,请使用默认实现中使用方法的自由函数(类似于std::begin)。

// 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的示例实现,以便完全看到这一点。


1 unique和shared都是不错的选择,具体取决于你是想在写入数据上存储不可变/复制,还是手动克隆。