53

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

但似乎可以用成员指针绕过这个限制,正如用户 chtz向我展示的那样:

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;
    }
};

在 coliru 上进行现场演示

为什么这是可能的,它是一个想要的特性还是实现或标准的措辞中的某个小故障?


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

4

4 回答 4

31

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

表达式&Derived::value (其类型是int Base::*完全符合标准,它指定 的value成员Base。那么表达式a_base.*pwherep是一个成员Basea_base一个实例的指针Base也是标准的。

因此,任何符合标准的编译器都应使表达式other.*(&Derived::value);定义行为:访问value.other

于 2018-03-29T09:43:51.553 回答
15

是黑客吗?

与 using 类似reinterpret_cast,这可能很危险,并且可能是难以发现的错误的来源。但它的格式很好,毫无疑问它是否应该起作用。

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

[是] 执行或标准措辞中的某个小故障吗?

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

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

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


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

不是UB。Base有会员。

于 2018-03-29T09:06:27.547 回答
-1

只是为了增加答案并放大一点我可以在你的字里行间读到的恐怖。如果您将访问说明符视为“法律”,要求您避免做“坏事”,我认为您没有抓住重点。public, protected, private, const... 都是系统的一部分,这对 C++ 来说是一个巨大的优势。没有它的语言可能有很多优点,但是当您构建大型系统时,这些东西是真正的资产。

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

几年前我可以简单地做到这一点(它可能仍然在某些环境中工作):

#define private public

对于“敌对”外部头文件非常有帮助。好习惯?你怎么看?但有时你的选择是有限的。

所以,是的,你所展示的是系统中的一种违规行为。但是,嘿,是什么让您无法获得和分发该成员的公开参考资料?如果可怕的维护问题让你兴奋——无论如何,为什么不呢?

于 2018-04-04T12:08:33.513 回答
-2

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

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

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

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

  1. 没有看到任何权限问题,因为您是在编译时访问的valuederived
  2. 可以做到这一点,因为你在一个具有相同结构的对象中以字节为单位获取偏移量derived(嗯,很明显,base)。

所以,你没有违反任何规则。您成功绕过了编译规则。你不应该这样做,正是因为你附加的链接中描述的原因,因为它破坏了 OOP 封装,但是,如果你知道你在做什么......

于 2018-03-29T08:35:54.180 回答