基础中的 dynamic_cast 与虚拟 AsDerived 方法

Posted

技术标签:

【中文标题】基础中的 dynamic_cast 与虚拟 AsDerived 方法【英文标题】:dynamic_cast vs virtual AsDerived method in base 【发布时间】:2016-08-23 08:17:29 【问题描述】:

当尝试访问派生类行为时,我读到的最常见的方法是使用dynamic_casts,即dynamic_cast<DerivedA*>(BasePtr)->DerivedAOnlyMethod()。这不是很漂亮,但每个人都明白发生了什么。

现在我正在编写一个代码,其中这种转换由导出到基类的虚拟函数处理,对于每个派生类,即:

class Base

public:
    virtual DerivedA* AsDerivedA()  throw Exception("Not an A"); 
    virtual DerivedB* AsDerivedB()  throw Exception("Not a B"); 
    // etc.
;
class DerivedA : public Base

public:
    DerivedA* AsDerivedA()  return this; 
;
// etc.

然后使用BasePtr->AsDerivedA()->DerivedAOnlyMethod()。恕我直言,这使基类变得混乱,并暴露了它不应该需要的派生类的知识。

我太缺乏经验,无法肯定地说哪个更好,所以我正在寻找支持和反对这两种结构的论据。哪个更惯用?他们在性能和安全性方面如何比较?

【问题讨论】:

这些函数打破了[开闭原则][1]。如果要添加 C 类,则需要更改 Base 类以添加 AsDerivedC。 [1]:en.wikipedia.org/wiki/Open/closed_principle 【参考方案1】:

好吧,将AsDerived@-methods 放入基类中肯定会导致更快的转换。

如果您使用final 限制继承层次结构,则该优势可能会降低或消除。

另外,你说它不常见是对的,因为它引入了混乱,并且它将所有相关派生类的知识引入了基类。

总而言之,它可能有时在遇到瓶颈时很有用,但你为这种可憎的事情付出代价。

【讨论】:

【参考方案2】:

如果没有看到更多代码,很难提供太多建议。但是,需要知道您调用的对象的类型更多地是使用变体而不是多态类型。

多态性是关于信息隐藏的。调用者不需要知道他持有什么类型。

大概是这样的吧?

struct base

    virtual bool can_do_x() const  return false; 
    virtual void do_x()  throw std::runtime_error("can't"); 
    virtual ~base() = default;
;

struct derived_a : base

    virtual bool can_do_x() const  return true; 
    virtual void do_x()  std::cout << "no problem!"; 
;

int main()

  std::unique_ptr<base> p = std::make_unique<derived_a>();
  if (p->can_do_x()) 
    p->do_x();
  

现在我们在谈论对象的能力,而不是类型。

【讨论】:

【参考方案3】:

你的直觉是对的,AsDerivedX 方法很混乱。在运行时可以检查这些虚函数是否被重载的事实相当于类型检查的成本。所以,在我看来,C++ 的做法是:

void doSomething(Base *unsureWhichAorB) 
    DerivedA *dA = dynamic_cast<DerivedA*>(unsureWhichAorB);
    if(dA) //if the dynamic cast failed, then dA would be 0
       dA->DerivedAOnlyMethod();

请注意,检查dA 的非零值在这里很关键。

【讨论】:

【参考方案4】:

您完全正确,这样的解决方案不仅会使基类变得混乱,而且还会对其产生不必要的依赖。在干净的设计中,基类不需要并且实际上不应该知道关于其派生类的任何事情。其他一切都将很快成为维护的噩梦。

但是,我想指出我属于“尽量避免dynamic_cast”团队。这意味着我经常看到 dynamic_cast 可以通过适当的设计避免。所以首先要问的问题是:为什么我需要知道派生类型?通常有一种方法可以通过正确使用多态来解决问题,或者“丢失”已经是错误的类型信息。

更喜欢使用多态而不是dynamic_cast

class Base

public:
    virtual void doSomething() = 0;
;

class DerivedA : public Base

public:
    void doSomething() override  //do something the DerivedA-way ;
;

class DerivedB : public Base

public:
    void doSomething() override  //do something the DerivedB-way ;
;
// etc.

【讨论】:

如果所需的函数在行为上是多态的,那么它们是基类中的虚函数。但有时您需要派生类特定的方法。 那么我会质疑将它们分配给Base 类型变量的决定。如果您 - 在某些时候 - 需要知道实际类型,为什么首先“扔掉它”? 将它们存储在一个通用容器中。 我猜是的 ;) 但是你为什么需要这样做呢?我的意思是显然它们并不完全相同,那么为什么要假装呢?也许Boost.Variant 是一个更好的选择呢? 我读过boost::variant,我会考虑的。现在,为了不把所有东西都扔掉,我会坚持选角。无论如何,谢谢,这很有帮助。

以上是关于基础中的 dynamic_cast 与虚拟 AsDerived 方法的主要内容,如果未能解决你的问题,请参考以下文章

使用指向基础抽象类的指针访问子类成员,该基础抽象类不能是 dynamic_cast

为啥 C-style cast 的行为与 dynamic_cast 不同?

关于继承中的强制类型转换

c++ 中的 dynamic_cast 有啥替代方法吗?

C++中的dynamic_cast和static_cast

dynamic_cast 用法