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

Pra*_*han 15 c++ inheritance factory type-erasure c++11

Sean Parent的讲话,继承是邪恶的基类,他说多态不是这种类型的属性,而是它的使用方式.作为一个拇指规则,不要使用继承来实现接口.这样做的许多好处之一是具有虚函数的类的虚拟化只是因为它们实现了一个接口.这是一个例子:

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;
};
Run Code Online (Sandbox Code Playgroud)

在这里,而不是UseDrawable要求mDraw是一个Drawable*,你可以有它使用一种擦除类能够包裹实施了一个名为成员的任何类draw.所以,boost::type_erasure::any具有适当定义的东西.这样,DrawA不需要继承Drawable- 多态性是真正UseDrawable的要求,而不是真正的属性DrawA.

我试图按照这个原则重构一些代码.我有一个抽象类ModelInterface和两个具体的类ModelAModelB继承自ModelInterface.继肖恩的建议,这是有道理的不给力ModelA,并ModelB进入继承层次结构和简单的使用类型擦除在需要满足通过建模的概念类的位置ModelInterface.

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

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

Yak*_*ont 17

类型擦除101:

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

struct exposed_type;
Run Code Online (Sandbox Code Playgroud)

此类公开了您要支持的概念.复制,移动,销毁,等于,总订单,哈希和/或您需要支持的任何自定义概念.

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
};
Run Code Online (Sandbox Code Playgroud)

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

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

第2步:编写类型擦除助手.

struct internal_interface;
Run Code Online (Sandbox Code Playgroud)

这里有纯虚拟接口. 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;
};
Run Code Online (Sandbox Code Playgroud)

在上面的常规类型中存储智能指针1.

struct exposed_type {
  std::unique_ptr<internal_interface> upImpl;
Run Code Online (Sandbox Code Playgroud)

将常规方法转发给帮助程序.例如:

exposed_type::exposed_type( exposed_type const& o ):
  upImpl( o.upImpl?o.upImpl->clone():nullptr )
{}
exposed_type::exposed_type( exposed_type&& o )=default;
Run Code Online (Sandbox Code Playgroud)

第3步:编写类型擦除实现.这是一个template存储a T并从helper继承的类,并将接口转发给T.std::begin如果没有找到无adl free函数,请使用默认实现中使用方法的自由函数(有点像).

// 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
  }
};
Run Code Online (Sandbox Code Playgroud)

第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)} )
{}
Run Code Online (Sandbox Code Playgroud)

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

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

查看示例实现,std::function以完全看到这一点.


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