肖恩·普恩特(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
的要求,而不是DrawA
的属性。我正在尝试遵循此原理重构一些代码。我有一个抽象类
ModelInterface
和两个具体的类ModelA
和ModelB
继承自ModelInterface
。按照Sean的建议,不要将ModelA
和ModelB
强制进入继承层次结构,而仅在需要一个满足ModelInterface
建模概念的类的位置上使用类型擦除,这是有道理的。现在,我的问题是我代码中当前使用
ModelInterface
的大多数位置也通过基于运行时配置文件构造适当的对象来这样做。当前,工厂将new
一个适当的对象并返回ModelInterface*
。如果我在代码的这些位置重构代码以使用类型擦除的概念(例如boost::type_erasure::any<implement ModelInterface>
之类的代码),那么如何在运行时构造此类对象? ModelA
和ModelB
是否仍需要是启用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/