c++ - 澄清 Sean Parent 的谈话 "Inheritance is the base class of evil"

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

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需要mDraw成为 Drawable* ,你可以让它使用一个类型删除的类,它可以包裹任何实现名为 draw 的成员的类.所以,像 boost::type_erasure::any具有适当的定义。那样,DrawA不需要从 Drawable 继承- 多态性真的是UseDrawable s 要求,而不是 DrawA 的真正属性.

我正在尝试按照这个原则重构一些代码。我有一个抽象类 ModelInterface和两个具体的类ModelAModelB继承自 ModelInterface .按照 Sean 的建议,不强制 ModelA 是有意义的。和 ModelB进入继承层次结构,并在需要满足 ModelInterface 建模的概念的类的位置简单地使用类型删除.

现在,我的问题是我的代码中的大多数地方当前使用 ModelInterface也可以通过基于运行时配置文件构造适当的对象来做到这一点。目前,工厂将new一个合适的对象并返回一个 ModelInterface* .如果我重构代码以在代码中的这些位置使用类型删除的概念(比如 boost::type_erasure::any<implement ModelInterface> ),我如何在运行时构造这样的对象?将ModelAModelB还需要启用 RTTI 的类(class)吗?或者我可以在没有 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 并从helper 继承,并将接口(interface)转发给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唯一和共享都是不错的选择,具体取决于您是要存储不可变/写入数据时复制,还是手动克隆。

关于c++ - 澄清 Sean Parent 的谈话 "Inheritance is the base class of evil",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26199126/

相关文章:

c++ - 如果您始终将其存储在 shared_ptr 中,您的接口(interface)是否需要虚拟析构函数?

C++ 字符数组作用域

c++ - 在 Qt 中使用 C++ 创建 mysql 触发器

c++ - Visual Studio 无法识别虚幻引擎

c++ - 如何默认包含某些头文件,这样我就不必在每个程序中都输入它们

python - 类型错误 : Error when calling the metaclass bases module

c++ - 模板元编程示例没有意义

c++ - 使用 Boost.Spirit.Lex 和流迭代器

c++ - 访问另一个子类中基类的 protected 成员

c++ - 涉及私有(private)继承的 C 风格向上转型和向下转型