从基类向下转换时是不是可以调用派生对象的虚拟方法?

Posted

技术标签:

【中文标题】从基类向下转换时是不是可以调用派生对象的虚拟方法?【英文标题】:Is it possible to call derived object's virtual method when down-casted from base class?从基类向下转换时是否可以调用派生对象的虚拟方法? 【发布时间】:2011-04-14 05:59:56 【问题描述】:

给定以下类结构:

class Base

    virtual void outputMessage()  cout << "Base message!"; 
;

class Derived : public Base

    virtual void outputMessage()  cout << "Derived message!"; 

.. 这个代码 sn-p:

Base baseObj;
Derived* convertedObj = (Derived*) &baseObj;
convertedObj->outputMessage();

.. 输出将是“基本消息!”。

有没有什么方法可以转换或操作对象以使 Derived 版本的 outputMessage 方法被多态调用?

编辑:我会尝试说明我为什么要这样做:

我正在编写与我们的主系统挂钩的迁移工具。出于这个原因,我需要访问受保护的成员方法,或者自定义现有的虚拟方法。前者我可以通过定义派生类并将对象转换为它来静态调用方法。我不能做的是改变我不静态调用的方法的行为(即在代码库的其他地方调用的方法)。

我也尝试过直接创建派生类的对象,但是由于对通过构造函数传递的对象的操作,这会导致系统的其他部分出现问题。

【问题讨论】:

为什么需要这个?实际上,当您必须执行此类任务时,这说明了设计问题。 我将尝试在问题中解释为什么会这样。 downcast problem in c++的可能重复 @Bo:我不这么认为 - 链接的问题似乎是在询问是否简单地转换为派生对象是否会为其他成员分配额外的内存,以及为什么使用它们似乎有效(显然回答“运气/未定义的行为”)。这个Q更多的是关于虚函数,其他没有提到。 @Tony - 好的,另一个问题使用 Derived 的数据而不是 Derived 的函数,但除此之外代码是相同的(包括类名:-) 问题是一样的 - 你不能这样做! 【参考方案1】:

不,虚函数对所指向对象的实际类型进行操作,在您的情况下,这只是一个简单的Base。 实际上,随着向下转换,你在这里进入了未定义的行为领域。这可能会像多继承的炸弹一样爆炸,派生类中的 vtable 与基类中的 vtable 的偏移量不同。

【讨论】:

【参考方案2】:

没有符合标准的解决方案

您尝试执行的操作无法使用 C++ 标准所保证的行为

如果您确实必须将此作为帮助迁移的短期措施,不要在生产中依赖它,并且可以充分验证行为,您可以进行实验,如下图所示。

讨论你的尝试

我所展示的是你采取了错误的方法:简单地将指向基址的指针转换为指向派生的指针不会改变对象的 vtable 指针。

推导出一个看似合理的 hack

解决这个问题,天真的方法是将对象重建为派生对象(“放置”new),但这也不起作用 - 它会重新初始化基类成员。

可以做的可能是创建一个非派生对象,它没有数据成员但有相同的虚拟调度表条目(即相同的虚拟函数,相同的可访问性私有/protected/public,相同的顺序)。

更多警告和注意事项

可能工作(就像在我的 Linux 机器上一样),但使用它需要您自担风险(我建议不要在生产系统上使用)。

进一步警告:这只能拦截虚拟调度,当编译器在编译时知道类型时,有时可以静态调度虚拟函数。

~/dev cat hack_vtable.cc
// change vtable of existing object to intercept virtual dispatch...

#include <iostream>

struct B

    virtual void f()  std::cout << "B::f()\n"; 

    std::string s_;
;

struct D : B

    virtual void f()  std::cout << "D::f()\n"; 
;

struct E

    virtual void f()  std::cout << "E::f()\n"; 
;

int main()

    B* p = new B();
    p->s_ = "hello";
    new (p) D();  // WARNING: reconstructs B members

    p->f();
    std::cout << '\'' << p->s_ << "'\n"; // no longer "hello"

    p->s_ = "world";
    new (p) E();
    p->f();  // correctly calls E::f()
    std::cout << '\'' << p->s_ << "'\n"; // still "world"


~/dev try hack_vtable   
make: `hack_vtable' is up to date.
D::f()
''
E::f()
'world'

【讨论】:

【参考方案3】:

好吧,即使您将 Base 对象转换为 Derived 对象,在内部,它仍然是 Base 对象:对象的 vftable(函数到 RAM 指针的实际映射)不会更新。 我不认为有任何方法可以做你想做的事,我不明白你为什么要这样做。

【讨论】:

【参考方案4】:

在这个问题downcast problem in c++ Rob 的答案也应该是你问题的答案。

【讨论】:

【参考方案5】:

至少在法律上不是这样。要调用 Derived 类函数,需要引用 Derived 对象。

【讨论】:

以上是关于从基类向下转换时是不是可以调用派生对象的虚拟方法?的主要内容,如果未能解决你的问题,请参考以下文章

从基类c ++的向量中的派生类调用虚拟方法[重复]

使用纯多态性和继承从基类调用派生类上的函数而不进行强制转换?

如何从基类调用派生类方法?

如何从基类调用派生赋值运算符?

派生类不从基类继承重载方法

从基类到不同派生类的多重继承转换