【问题标题】:Get derived type via base class virtual function通过基类虚函数获取派生类型
【发布时间】:2013-10-21 18:20:15
【问题描述】:

我正在尝试通过基类虚函数获取对象的派生类型。我已经写了这个,它不会编译:

struct base {
  virtual base& get_this() {
    return *this;
  }
};

struct derived : base {
  virtual derived& get_this() override {
    return *this;
  }

  void fn();
};


int main () {
  base* pd = new derived();
  derived& x = pd->get_this(); /*ERROR*/
  x.fn();
  return 0;
}

...给我一个错误:我无法从base 初始化derived&。既然get_this 是虚拟的,为什么pd->get_this() 返回base& 而不是derived&?提前致谢!

编辑:

感谢大家的有用回答,并为我迟到的回复道歉。我应该在原始帖子中指定我也对我的问题的解决方案感兴趣,而不仅仅是弄清楚为什么上述内容无法编译。我的主要问题是 fnderived 类独有的,不能通过基类调用。使用强制转换确实可以解决问题,但我讨厌使用 if else 构造来编写代码以获得正确的类型(Scott Meyers 也建议不要使用强制转换:))。答案似乎表明演员阵容是要走的路,这在某种程度上至少让人放心,我并没有忽视对我的问题的更“优雅”的解决方案。再次感谢!

【问题讨论】:

  • 删除了我的答案,但我仍然相信 OP 使用简单的dynamic_cast 会更好,这对于所描述的用例来说将是一个可行的解决方案。没有其他人在答案中提到这一点,并且 C++ 中支持协方差的方式并不能解决 OP 的主要问题。
  • @g-makulik 嗯,是的,你可以(应该)删除或修改有争议的句子——其余的都是正确的,一个很好的答案。现在你已经把孩子和洗澡水一起扔了。
  • @KonradRudolph 给了另一个(省略了挑剔的词)。至少你是对的:当时的投票率低于声誉增益。但是我对否决票感到很紧张,经验表明,他们永远不会因为经过适当编辑的答案而被撤回。

标签: c++ types derived


【解决方案1】:

C++ covariant return types 支持只会起作用,只要您已经知道派生类型。要将基类downcast 转换为可能的派生类,只需使用dynamic_cast<derived>(base_ref) 来确定 base_ref 是否与实际派生类型匹配:

int main () {
    base* pd = new derived();
    derived& x = dynamic_cast<derived&>(*pd); // Will throw an exception if pd 
                                          // isn't a 'derived'
    x.fn();
    return 0;
}

或者:

int main () {
    base* pd = new derived();
    derived* x = dynamic_cast<derived*>(pd); // Will return nullptr if pd isn't
                                         // a 'derived'
    if(x) {
        x->fn();
    }
    else {
        // dynamic_cast<derived*> failed ...
    }
    return 0;
}

支持派生类的协变返回类型,但正如其他答案所述,您无法通过在此处调用基类 (pd-&gt;get_this()) 来获取它。

如果您不能使用 RTTI、异常处理或想要紧密的类型绑定(没有 vtable 开销),您也可以考虑使用 static polymorphism 在编译时检查类型合规性。

【讨论】:

    【解决方案2】:

    C++ 支持协变返回类型。 这意味着当您通过base 指针在derived 对象上调用get_this() 时,将调用派生的实现。

    但这并不意味着拨打base::get_this 会给您derived&amp;base::get_this 的返回类型是 base&amp;。如果你想得到一个derived 对象,你必须通过derived 指针调用get_this(或者将你的base&amp; 向下转换为derived&amp;)。请注意,这就是返回类型协方差在 Java、C++、D...中的工作方式...

    base* pbase = new base();
    base* pderived = new derived();
    derived* pderived2 = new derived();
    
    base& a = pbase->get_this();        // call implementation in base, return base&
    base& b = pderived->get_this();     // call implementation in derived, return base&
    derived& c = pderived2->get_this(); // call implementation in derived, return derived&
    

    【讨论】:

    • 所以c++ 协变返回类型在我们谈论派生类的类型本身时非常无用,不是吗?
    • @g-makulik 事实上,我不知道任何具有协变返回类型的语言不能像这样工作:/ 当您操作 base 引用时,它可能是后面的任何类型,那么你怎么知道x...的类型应该是什么?
    • 我要补充一点,“派生”与“占卜”不同——您定义了基类的派生词,通常会建设性地扩展和完善每个基类。这是推导,但是根据您认为“有用”的定义的推导更接近于逆。这通常是您为抽象付出的代价。
    【解决方案3】:

    pd 的静态类型是base *。因此,当编译器查找成员函数get_this() 时,它只找到base::get_this()base::get_this() 的返回类型为base&amp;,不能转换为derived&amp;。因此出现错误。

    【讨论】:

    • 我同意这完美地回答了为什么 OP 在编译时出现错误,但没有显示出消除错误的可行替代方案。
    【解决方案4】:

    我找到了一个简单的解决方案,但如果可以的话,我希望高手评估一下:

    class base{ 
        type = 1;
        virtual int getType() final {
            return type;
        }
    }
    
    class derived1 : public base {
        derived1(){
            type = 2;
        }
    }
    

    这样,您可以调用任何派生类的方法“int getType()”。由于类型是在构造函数上设置的,因此不存在行为不当的风险。 为了提高可用性,我创建了一个预定义的“类型”。

    我正在使用,但我不知道是不是 MacGyvery!

    【讨论】:

    • 所有这些方法都是私有的,type 缺少类型说明符。在您的示例中,您甚至无法实例化 derived1 的实例。
    • 上面的解决方案也会让你头疼:)
    • 这似乎是基于“魔术数字”的“鸭子打字”,或者是手动/详尽的“枚举类型”减去唯一性和集中索引。如果不预测头痛,我就是一个“大师”;我必须支持@Alexander。
    【解决方案5】:

    我想通过参考working draft C++ standard (click here) 的第 10.3 节第 8 段来补充 Novelocrat 的答案,其中解释了在这种情况下返回的指针的静态类型是 Derived* 而不是 Base*。基本上,如果您通过指向派生类的指针调用get_this(),那么您将获得正确的类型而不会出现编译器错误。

    以下是标准的引用以及示例(也来自标准):

    如果 D::f 的返回类型与 B::f 的返回类型不同,则 D::f 的返回类型中的类类型应在该点完成 D::f 的声明或应为类类型 D。当 覆盖函数被称为被覆盖的最终覆盖者 函数,它的结果被转换为返回的类型 (静态选择)覆盖函数(5.2.2)。 [示例:

    class B { };
    class D : private B { friend class Derived; };
    struct Base {
        virtual void vf1();
        virtual void vf2();
        virtual void vf3();
        virtual B* vf4();
        virtual B* vf5();
        void f();
    };
    
    struct No_good : public Base {
        D* vf4(); // error: B (base class of D) inaccessible
    };
    
    class A;
    struct Derived : public Base {
        void vf1(); // virtual and overrides Base::vf1()
        void vf2(int); // not virtual, hides Base::vf2()
        char vf3(); // error: invalid difference in return type only
        D* vf4(); // OK: returns pointer to derived class
        A* vf5(); // error: returns pointer to incomplete class
        void f();
    };
    
    void g() {
        Derived d;
        Base* bp = &d; // standard conversion:
        // Derived* to Base*
        bp->vf1(); // calls Derived::vf1()
        bp->vf2(); // calls Base::vf2()
        bp->f(); // calls Base::f() (not virtual)
        B* p = bp->vf4(); // calls Derived::pf() and converts the
        // result to B*
        Derived* dp = &d;
        D* q = dp->vf4(); // calls Derived::pf() and does not
        // convert the result to B*
        dp->vf2(); // ill-formed: argument mismatch
    }
    

    【讨论】:

    • '如果您通过指向派生类的指针调用 get_this(),那么您将获得正确的类型而没有编译器错误' 所以c++ covariant return当我们谈论派生类的类型本身时,类型是毫无用处的,不是吗?
    • @g-makulik 它们确实允许您在从派生类型调用方法时获得正确的类型。否则你将不得不投射。
    • OP 用例的关键点是,您需要知道派生类型才能获得函数的正确协变返回类型,因此它对向下转换没有太大帮助(显然是 OP 想要的)。
    • @g-makulik 我从来没有声称我的回答的目的是提出一个替代方案。我只是用标准的适当摘录来支持 Novelocrat 的答案,我认为这很有启发性。我没有建议使用 dynamic_cast 进行向下转换,主要是因为我认为大多数时候它是一个可疑的解决方案。根据他的需要,也许更好的解决方案是简单地添加一个适当的虚函数,根据动态对象类型正确执行正确的代码。我的意思是,这就是最初发明虚函数的原因。
    • @g-makulik 此外,OP 明确要求的唯一一件事就是他为什么会出现编译错误。
    猜你喜欢
    • 2012-08-08
    • 2012-01-07
    • 2015-10-24
    • 1970-01-01
    • 1970-01-01
    • 2015-03-30
    • 2016-10-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多