所以有一个任务:我们有一个第三方库,有一个类(称之为Base)。该库提供了一个隐藏的实现,称为 Impl。 我需要写一个代理。不幸的是 Base 有一个 protected 虚拟函数 fn。
所以问题是,从 C++ 的角度来看,下面的代码在多大程度上是正确的?它目前在 Visual Studio 中完美运行,但在 Mac 上的 clang/gcc 中不起作用(但编译时没有任何警告)。我很清楚那里发生的机制,所以如果删除类问题,一切都可以在两个平台上运行。我想知道我是否应该向 clang 报告错误,或者它是 C++ 标准的未定义/未指定行为。
代码的预期结果是正常调用 Impl::fn()
class Base
{
protected:
virtual void fn(){}
};
class Impl : public Base
{
public:
Impl() : mZ(54){}
protected:
virtual void fn()
{
int a = 10; ++a;
}
int mZ;
};
class Problem
{
public:
virtual ~Problem(){}
int mA;
};
class Proxy : public Problem, public Base
{
public:
virtual void fn()
{
Base * impl = new Impl;
typedef void (Base::*fn_t)();
fn_t f = static_cast<fn_t>(&Proxy::fn);
(impl->*f)();
delete impl;
}
};
int main()
{
Proxy p;
p.fn();
}
最佳答案
它恰好在这一行崩溃:
(impl->*f)();
尝试访问已分配 block 后面的内存。这通常表明没有正确设置this
,事实上,交换继承顺序可以解决问题,从而证实了这一理论。
Base * impl = new Impl;
typedef void (Base::*fn_t)();
fn_t f = static_cast<fn_t>(&Proxy::fn);
(impl->*f)();
所以问题实际上是 fn_t 指向的位置(当然不是这里的 Base::fn 的 vtable 条目)。
现在我们真正看到了这个问题。您尝试调用另一个对象的 protected 函数,尝试使用 &Base::fn 因为这是不可能的,尝试使用指向 Proxy::fn 的指针实际上是一个不同的函数,具有不同的 vtable 索引,这不会存在于基地中。
现在这之所以有效,只是因为 MSVC 使用不同的内存布局,而 Proxy::fn 和 Base::fn 恰好具有相同的 vtable 索引。尝试在 MSVC 构建中交换继承顺序,它可能会崩溃。或者尝试在某处添加另一个函数或成员,我猜迟早它也会与 MSVC 一起崩溃。
关于基本思想:我们在这里试图完成的是调用不同对象的 protected 函数。引用this列表,本质上是相同的 here
Class members declared as protected can be used only by the following:
- Member functions of the class that originally declared these members.
- Friends of the class that originally declared these members.
- Classes derived with public or protected access from the class that originally declared these members.
- Direct privately derived classes that also have private access to protected members.
- 事实并非如此
- 没有宣布好友
- 尝试调用不同对象的方法,而不是
this
- 事实并非如此
所以我认为这是不合法的,会导致未定义的行为,对任何巧妙的转换都漠不关心等。
关于c++ - 调用另一个相关对象的 protected 虚函数(用于代理),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26256083/