【问题标题】:dynamic_cast vs virtual AsDerived method in base基础中的 dynamic_cast 与虚拟 AsDerived 方法
【发布时间】: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()。恕我直言,这使基类变得混乱,并暴露了它不应该需要的派生类的知识。

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

【问题讨论】:

标签: c++ inheritance casting


【解决方案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,我会考虑的。现在,为了不把所有东西都扔掉,我会坚持选角。无论如何,谢谢,这很有帮助。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-09-30
        • 2015-06-13
        • 1970-01-01
        • 2017-11-10
        • 1970-01-01
        • 1970-01-01
        • 2023-03-12
        相关资源
        最近更新 更多