通过成员指针访问受保护的成员:这是一个黑客?

时间:2018-03-29 07:48:21

标签: c++ language-lawyer protected access-specifier member-pointers

我们都知道基类中指定的protected成员只能从派生类自己的实例访问。这是标准的一个特性,这已在Stack Overflow上多次讨论过:

但似乎可以使用成员指针来解决这个限制,例如用户chtz has shown me

struct Base { protected: int value; };
struct Derived : Base
{
    void f(Base const& other)
    {
        //int n = other.value; // error: 'int Base::value' is protected within this context
        int n = other.*(&Derived::value); // ok??? why?
        (void) n;
    }
};

Live demo on coliru

为什么这可能,在实施或标准的措辞中是某个想要的功能还是故障?

从评论中出现了另一个问题:if Derived::f is called with an actual Base,是不确定的行为?

4 个答案:

答案 0 :(得分:29)

由于访问控制 expr.ref,无法使用类成员访问 [class.access]aclass.amember)访问成员不会使用其他表达式使该成员无法访问。

表达式&Derived::value (whose type is int Base::*)完全符合标准,并指定value的成员Base。然后,表达式a_base.*p p表示Base成员的指针a_base Base other.*(&Derived::value); value。{/ {}} p>

因此,任何符合标准的编译器都应使表达式other定义为行为:访问ionic cordova platform update android 的成员ionic cordova platform add android@latest

答案 1 :(得分:14)

  

是黑客吗?

与使用reinterpret_cast类似,这可能很危险,并且可能是很难找到错误的来源。但它已经形成良好,毫无疑问它是否应该有效。

澄清类比:reinterpret_cast的行为也完全在标准中指定,可以在没有任何UB的情况下使用。但是reinterpret_cast规避了类型系统,类型系统是有原因的。类似地,这个指向成员技巧的指针很好地根据标准形成,但它绕过了成员的封装,并且封装(通常)存在是有原因的(我通常说,因为我认为程序员可以轻率地使用封装)。 / p>

  

[是]在执行中的某个地方还是标准的措辞?

不,实施是正确的。这就是指定语言的工作方式。

Derived的成员函数显然可以访问&Derived::value,因为它是基础的受保护成员。

该操作的结果是指向Base成员的指针。这可以应用于对Base的引用。成员访问权限不适用于指向成员的指针:它仅适用于成员的名称。

  

从评论中出现了另一个问题:如果使用实际的Base调用Derived :: f,它是否是未定义的行为?

不是UB。 Base有会员。

答案 2 :(得分:-1)

只是为了添加答案并放大我可以在你的行之间阅读的恐怖片段。如果你认为访问说明符是“法律”,那就是为了防止你做坏事,我认为你错过了这一点。 publicprotectedprivateconst ...都是系统的一部分,对C ++来说是一个巨大的优势。没有它的语言可能有许多优点,但是当你构建大型系统时,这些东西才是真正的资产。

话虽如此:我认为可以绕过提供给你的几乎所有安全网是件好事。只要你记得那可能的'并不意味着“好”。这就是为什么它永远不应该是“容易”的原因。但对于其他人 - 它取决于你。你是建筑师。

多年前我可以简单地做到这一点(它可能仍然适用于某些环境):

#define private public

非常有用的敌对'外部头文件。好的做法?你怎么看?但有时你的选择是有限的。

所以是的,你所展示的是系统中的一种破坏。但是,嘿,是什么让你无法获得并向会员分发公开引用?如果可怕的维护问题让你失望 - 无论如何,为什么不呢?

答案 3 :(得分:-2)

基本上你正在做的是欺骗编译器,这应该有效。我总是看到这样的问题,人们有时会得到不好的结果,有时它会起作用,这取决于它如何转换为汇编程序代码。

我记得在一个整数上看到一个带有const关键字的案例,但随后有了一些技巧,这个人能够改变价值并成功规避了编译器的意识。结果是:一个简单的数学运算的错误值。原因很简单:x86中的汇编确实区分了常量和变量,因为某些指令在其操作码中确实包含常量。因此,由于编译器认为它是一个常量,它会将它视为一个常量并以错误的CPU指令以优化的方式处理它,并且baam,你得到的数字有一个错误

换句话说:编译器将尝试强制执行它可以强制执行的所有规则,但最终可能会欺骗它,并且您可能会或可能不会根据您尝试执行的操作得到错误的结果,因此您会更好只有当你知道自己在做什么时才做这些事情。

在您的情况下,指针&Derived::value可以从对象计算出从类的开头有多少字节。这基本上是编译器访问它的方式,因此,编译器:

  1. 看不到任何权限问题,因为您在编译时通过value访问derived
  2. 可以这样做,因为你在与derived具有相同结构的对象中获取字节偏移量(显然,base)。
  3. 所以,你没有违反任何规则。您成功绕过了编译规则。你不应该这样做,完全是因为你所附链接中描述的原因,因为它打破了OOP封装,但是,如果你知道你正在做什么......