调用另一个相关对象的受保护虚函数(用于代理)
Posted
技术标签:
【中文标题】调用另一个相关对象的受保护虚函数(用于代理)【英文标题】:Calling protected virtual function of another related object (for proxying) 【发布时间】:2014-10-08 11:42:16 【问题描述】:所以一个任务:我们有一个第三方库,有一个类(叫它Base)。库提供了一个隐藏的实现,称为 Impl。 我需要写一个代理。不幸的是 Base 有一个受保护的虚函数 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();
【问题讨论】:
【参考方案1】:它恰好在这一行崩溃:
(impl->*f)();
试图访问分配块后面的内存。这通常暗示一个人没有正确设置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 条目)。
现在我们真正看到了这个问题。您尝试调用另一个对象的受保护函数,尝试使用 &Base::fn 是不可能的,尝试使用指向 Proxy::fn 的指针实际上是一个不同的函数,具有不同的 vtable 索引,它不会存在于 Base 中。
现在这工作只是因为 MSVC 使用不同的内存布局,巧合的是 Proxy::fn 和 Base::fn 具有相同的 vtable 索引。尝试在 MSVC 构建中交换继承顺序,它可能会崩溃。或者尝试在某处添加另一个函数或成员,我猜它迟早会与 MSVC 崩溃。
关于基本思想:我们在这里尝试完成的是调用一个不同对象的受保护函数。参考this列表,基本上和here说的一样
声明为受保护的类成员只能由以下人员使用:
最初声明这些成员的类的成员函数。 最初声明这些成员的类的朋友。 从最初声明这些成员的类派生的具有公共或受保护访问权限的类。 直接私有派生类也可以私有访问受保护的成员。
-
并非如此
没有朋友声明
试图在不同的对象上调用方法,而不是
this
并非如此
所以我认为这是不合法的,导致未定义的行为,对任何巧妙的转换等都漠不关心。
【讨论】:
谢谢,我知道它是如何工作的以及它为什么会崩溃。它不会与 MSVC 一起崩溃。我想知道的是这是否是一个正确的(虽然有点奇怪)c++ 代码,它必须在任何地方工作。顺便说一句,我很高兴你意识到微妙之处在哪里以及它应该如何工作:) 我不认为这是定义明确的行为,因为您实际上是在调用 不同 对象的受保护函数,这是不合法的。 @dev_null 你是什么意思正确?您故意规避标准来做被指定为非法的事情。当然,标准的重点不是“必须在任何地方工作”:定义必须工作的内容。 tl;博士:没有。 关于下一次编辑后的视觉工作室:不,它独立于类的顺序和虚拟功能的数量。恕我直言,这是正确的。 VS 使用另一个模型来表示 pmfs(指向成员函数的指针)。关于调用受保护函数:如果对象属于同一类型,我可以在另一个对象上调用它。即调用 mMyOtherProxyObject 的 Proxy::fn() 是完全可以的。这里最重要的是指针的转换(从 Proxy 到 Base 以及从 Base 到 Impl)。我什至可以改变问题:如果这里的一切都是“公开的”怎么办。该示例应该有效吗? 好吧,如果一切都是公开的,你就可以直接通过基指针调用函数...【参考方案2】:问题是您从Base
和Problem
中多重继承。类的 ABI 布局不是由标准定义的,实现可以选择如何布局对象,这就是为什么在不同的编译器上会看到不同的结果。
具体来说,崩溃的原因是您的派生类最终有两个 v-table:Base
和 Problem
各一个。
我是 g++ 的情况,因为你继承了 public Problem, public Base
,所以类布局在“传统”位置有 Problem
的 v-table,在类布局后面有 Base
的 v-table。
如果您想查看此操作,请将其添加到您的 main
...
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)();
因为您正在获取Proxy
的成员函数指针并将其应用于Impl
对象。是的,它们都继承自Base
,但是你给了它一个类Proxy
的成员函数指针,当它查找那个v-table 时,它们位于不同的位置。
您真的只想获取Base
的成员函数指针,但由于您是在Proxy
的上下文中执行此操作,因此您只能访问Proxy
成员函数。现在应该很明显,由于多重继承,这是不可取的。
但是,您可以通过一个小助手类轻松获得我认为您想要的内容...
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;
因为Helper
继承自Base
,所以它可以访问受保护的成员,你可以在Proxy
的多重继承上下文之外访问它。
【讨论】:
谢谢,因为代理在我身边,我可以选择继承顺序。以及我意识到多重继承的问题。主要问题是代码的正确程度(独立于编译器和继承顺序)。我有权利期望这将适用于例如英特尔编译器,或 10 年后在另一个 CPU/平台上的 C++ 编译器。 你根本不能对它做任何假设,除非你做你正在做的事情可能会与不同的编译器一起破坏。帮助程序解决方法应该提供您想要的任何编译器。以上是关于调用另一个相关对象的受保护虚函数(用于代理)的主要内容,如果未能解决你的问题,请参考以下文章