【问题标题】:Cast derived virtual override to base pure virtual member将派生的虚拟覆盖强制转换为基本纯虚拟成员
【发布时间】:2016-07-28 13:25:36
【问题描述】:

我理解为什么不能像 here 解释的那样简单地将派生类成员函数指针转换为基类成员函数指针。

但是,鉴于这个 sn-p:

struct base
{
    virtual void foo() = 0;
};

struct derived : base
{
    void foo() override {};
};

struct invoker
{
    typedef void(base::*target)();

    invoker(base* b, target t)
    {
        (b->*t)();
    }
};

template<typename B, typename D>
void (B::*cast(void (D::*method)()))()
{
    return static_cast<void(B::*)()>(method);
}

derived d;
invoker bad(&d, &derived::foo); //C2664
invoker good(&d, cast<base>(&derived::foo));

我想问是否可以装饰基本函数签名,以便编译器理解它是一个纯虚方法,并且它将在层次结构的某个地方实现(否则我无法构造B类型的对象) ?我理解为什么我不能用普通函数做到这一点,但恕我直言,如果是纯虚函数,编译器保证它会被实现(如果没有完成,我会得到一个关于类 B 的错误不是关于演员)。

【问题讨论】:

  • 不清楚你要什么样的装饰。如果你是 C++ 设计委员会的最高负责人,你会怎么做?不要担心一致性或正确性,只需显示您想要添加到 C++ 中的内容。如果你只是想告诉编译器derived::foo存在于base中,你可以简单地写成&amp;base::foo
  • @n.m.我想避免做出明确的演员表:D 但它更像是“有什么我不知道的方式”类型的问题。
  • 不,不做演员表。只需从&amp;base::foo 开始。 &amp;derived::foo 能做什么&amp;base::foo 不能?
  • @n.m - 你真的让我大吃一惊。我试过了并进入了派生函数。你能解释一下 vtable 魔法发生在哪里吗?我正在传递一个指向base::foo 的指针,它是纯虚拟的。我通过函数指针调用它(这是否涉及 vtable 查找?)。谢谢:)
  • 指向成员函数的指针尊重虚拟性,就像普通的成员函数语法一样。通常这是通过在 ptr-to-mem 中的某处存储 vtable 偏移量来实现的。

标签: c++ inheritance virtual-functions pointer-to-member


【解决方案1】:

无需操作&amp;derived::foo 的类型。可以改用&amp;base::foo

指向成员函数的指针尊重虚拟性。这个电话

base* pBase = new derived;
auto pFoo = &base::foo;
(pBase->*pFoo)();

实际上会调用derived::foo,就像简单的调用pBase-&gt;foo()一样。

【讨论】:

    【解决方案2】:

    即使保证已实现,它也可能使用仅在 derived 中声明的其他数据成员。一种可能的解决方案是使用函数指针并将this 作为第一个参数传递(这也向您展示了为什么您不能通过virtual 执行此操作)。

    【讨论】:

    • 由于我传递了base*,由于运行时多态性,我最终会得到正确的覆盖,所以我无法访问任何不存在的东西。还是我误会了你?
    • int i; 添加到派生并执行i = 0xDEADBEEF;derived::foo() 以了解我的意思。它以相反的方式工作:您可以将Base 函数指针传递给期望derived 的函数指针。请注意,知道你正在传递derived*,但 C++ 编译器可能不知道它..
    • 不,我不能,因为我不能构造Base 的实例,因为它有一个纯虚拟成员。我不是在谈论一般情况,而是关于演员目标是Base 的纯虚拟成员的情况。
    • 好的,有了上面的内容并且有实现这个方法的struct derived2 : base。现在,您最终可能会将derived2* 作为base*derived 成员函数指针传递。
    • 当我写这个问题时,我有一个假设,即 vtable 查找以某种方式神奇地发生在函数指针上,但它不可能发生,所以问题是不正确的,抱歉:)
    【解决方案3】:

    考虑以下菱形层次结构:

    struct base {
    virtual void foo() = 0;
    };
    
    struct D1 : public virtual base {
    virtual void foo() override;
    };
    
    struct D2 : public virtual base {
    virtual void foo() override;
    };
    
    struct Derived : public virtual D1, D2 {
    virtual void foo() final;
    };
    

    现在考虑一个允许从 Derived::* 向上转换到 base::* 的场景。应该调用哪个函数?编译器会丢失有关您希望调用的 D1::foo、D2::foo 或 Derived::foo 中的哪一个的信息,因为该信息已被丢弃。为了避免这种模棱两可的情况,这种向上转换是不允许的。

    【讨论】:

    • 我想我的失败点是 vtable 查找也发生在函数指针上(在这种情况下,实际上会调用正确的方法),但我想它不会发生。
    • "应该调用哪个函数?" foo(),我猜
    猜你喜欢
    • 2011-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-02
    • 2015-10-23
    • 2021-11-01
    • 2021-09-24
    相关资源
    最近更新 更多