C++ 调用另一个相关对象的受保护虚拟函数(用于代理)

C++ 调用另一个相关对象的受保护虚拟函数(用于代理),c++,gcc,clang,cl,C++,Gcc,Clang,Cl,所以有一个任务:我们有一个第三方库,有一个类(称为Base)。库提供了一个隐藏的实现,称为Impl。 我需要写一个代理。不幸的是,Base有一个受保护的虚拟函数fn 问题是下面的代码从C++的观点来看是正确的?它目前在VisualStudio中运行良好,在Mac上的clang/gcc中不起作用(但编译时没有任何警告)。我很清楚发生在那个里的机制,所以若去掉类的问题,一切都可以在两个平台上运行。我想知道我是否应该报告一个错误,以CLANG或它的未定义/未指定的行为C++标准。 代码的预期结果是正常

所以有一个任务:我们有一个第三方库,有一个类(称为Base)。库提供了一个隐藏的实现,称为Impl。 我需要写一个代理。不幸的是,Base有一个受保护的虚拟函数fn

问题是下面的代码从C++的观点来看是正确的?它目前在VisualStudio中运行良好,在Mac上的clang/gcc中不起作用(但编译时没有任何警告)。我很清楚发生在那个里的机制,所以若去掉类的问题,一切都可以在两个平台上运行。我想知道我是否应该报告一个错误,以CLANG或它的未定义/未指定的行为C++标准。 代码的预期结果是正常调用Impl::fn()

类基
{
受保护的:
虚空fn(){}
};
类Impl:公共基
{
公众:
Impl():mZ(54){}
受保护的:
虚空fn()
{
INTA=10;++a;
}
int-mZ;
};
阶级问题
{
公众:
虚拟~Problem(){}
int-mA;
};
类代理:公共问题,公共基
{
公众:
虚空fn()
{
基本*impl=新的impl;
typedef void(基::*fn_t)();
fn\u t f=静态强制转换(&Proxy::fn);
(impl->*f)();
删除impl;
}
};
int main()
{
代理p;
p、 fn();
}

它正好在这一行崩溃:

    (impl->*f)();
试图访问分配的块后面的内存。这通常是一个提示,提示您没有正确设置
,实际上,交换继承顺序解决了问题,证实了这一理论

    Base * impl = new Impl;

    typedef void (Base::*fn_t)();
    fn_t f = static_cast<fn_t>(&Proxy::fn);
    (impl->*f)();
Base*impl=新的impl;
typedef void(基::*fn_t)();
fn\u t f=静态强制转换(&Proxy::fn);
(impl->*f)();
因此,问题实际上是fn_t指向的地方(这里肯定不是Base::fn的vtable条目)

现在我们真正看到了这个问题。您尝试调用另一个对象的受保护函数,尝试使用&Base::fn因为这是不可能的,尝试使用指向Proxy::fn的指针实际上是一个不同的函数,具有不同的vtable索引,这在Base中不存在

现在之所以这样做是因为MSVC使用了不同的内存布局,其中Proxy::fn和Base::fn恰好具有相同的vtable索引。尝试在MSVC构建中交换继承顺序,它可能会崩溃。或者尝试在某处添加另一个函数或成员,我想它迟早也会在MSVC中崩溃

关于基本思想:我们试图在这里实现的是调用不同对象的受保护函数。关于清单,基本上是一样的

声明为受保护的类成员只能由以下人员使用:

  • 最初声明这些成员的类的成员函数
  • 最初声明这些成员的类的朋友
  • 从最初声明这些成员的类派生的具有公共或受保护访问权限的类
  • 直接私有派生类,这些类也可以私有访问受保护的成员
  • 情况并非如此
  • 没有朋友宣布
  • 试图在不同的对象上调用方法,而不是
  • 情况并非如此

  • 因此,我认为这是不合法的,会导致未定义的行为,对任何巧妙的施法都漠不关心等。

    问题在于,您是从
    基础
    问题
    双重继承的。标准没有定义类的ABI布局,实现可以选择如何布局对象,这就是为什么在不同的编译器上看到不同的结果

    具体来说,崩溃的原因是派生类最终有两个v形表:一个用于
    Base
    Problem

    在g++案例中,由于继承了
    public Problem,public Base
    类布局在“传统”位置有
    Problem
    的v表,在类布局的后面有
    Base
    的v表

    如果您想看到此操作,请将其添加到您的
    main

    int main()
    {
        Proxy p;
        Base *base = &p;
        Problem *problem = &p;
        std::cout << "Proxy: " << &p << ", Problem: " << problem << ", Base: " << base << '\n';
    }
    
    现在,你在做一些“邪恶”的事情:


    由于
    Helper
    继承自
    Base
    它可以访问受保护的成员,并且您可以在
    Proxy

    的多重继承上下文之外访问它。谢谢,我知道它是如何工作的以及为什么会崩溃。而且它不会在MSVC上崩溃。我想知道的是,这是否是一个正确的(尽管有点奇怪)C++代码,它必须在任何地方工作。顺便说一句,我很高兴你意识到微妙性在哪里以及它应该如何工作:)我认为这不是定义良好的行为,因为你实际上是在调用另一个对象的受保护函数,这是不合法的。@dev\u null你说的对吗?你是故意违反标准去做一些被指定为非法的事情。当然,标准的重点不是“必须在任何地方都有效”:定义什么必须有效。tl;dr:不。关于visual studio,在您下一次编辑之后:不,它根据类的顺序和虚拟函数的数量独立工作。我觉得这是对的。VS使用另一个模型来表示pmfs(指向memeber函数的指针)。关于调用受保护函数:如果另一个对象的类型相同,我可以调用它。调用mMyOtherProxyObject的Proxy::fn()完全可以。这里最重要的是指针的转换(从代理到基,从基到Impl)。我甚至可以改变这个问题:若这里的一切都是“公开的”,那个该怎么办。这个例子是否可行?好吧,如果一切都是公共的,那么你可以通过基指针直接调用函数……谢谢,因为代理在我这边,所以我可以选择继承顺序。我也意识到多重继承的问题。同样,主要的问题是代码的正确程度(独立于编译器和继承顺序)。这是我有权利期待这将在英特尔公司的工作
    int main()
    {
        Proxy p;
        Base *base = &p;
        Problem *problem = &p;
        std::cout << "Proxy: " << &p << ", Problem: " << problem << ", Base: " << base << '\n';
    }
    
    Proxy: 0x7fff5993e9b0, Problem: 0x7fff5993e9b0, Base: 0x7fff5993e9c0
    
    typedef void (Base::*fn_t)();
    fn_t f = static_cast<fn_t>(&Proxy::fn);
    (impl->*f)();
    
    virtual void fn()
    {
        typedef void (Base::*fn_t)();
        struct Helper : Base {
          static fn_t get_fn() { return &Helper::fn; }
        };
    
        Base * impl = new Impl;
        fn_t f = Helper::get_fn();
        (impl->*f)();
        delete impl;
    }