通过成员指针访问受保护的成员:这算作一种黑客行为吗?

55

我们都知道从一个基类指定为 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 上的实时演示

为什么这是可能的?这是一个想要的功能还是实现中存在的某个故障或标准措辞问题?


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


评论不适合进行长时间的讨论;此对话已被移至聊天室 - user3956566
2
@YvetteColomb 这是一个集体努力,旨在找到解决问题/改进问题的方法。难道不能把它们放回去吗?它们里面仍然有一些信息可以完善被接受的答案。 - YSC
它们都仍然在链接的聊天中。 - user3956566
1
这拯救了我的一天。请注意,方法f可以是静态的,这有助于避免实际上创建Derived对象。 - 463035818_is_not_a_number
4个回答

32

一个成员使用 类成员访问表达式 expr.refaclass.amember)因为 访问控制 [class.access] 而无法访问,但这并不意味着这个成员不能用其他表达式访问。

表达式 &Derived::value (类型为 int Base::* 完全符合标准,并指代了 Base 的成员 value。那么当 p 是指向 Base 成员的指针,a_baseBase 的实例时,表达式 a_base.*p 也是符合标准的

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


15

这是一个黑客攻击吗?

类似于使用 reinterpret_cast,这可能很危险,并且可能成为难以发现的错误源。但它是合法的,没有疑问它是否应该工作。

为了澄清这个比喻: reinterpret_cast 的行为也在标准中明确定义,并且可以在没有任何未定义行为情况下使用。但是 reinterpret_cast 绕过了类型系统,而类型系统存在有其原因。同样,根据标准,这种指向成员的指针技巧是符合规范的,但绕过了成员的封装,而封装(通常)存在有其原因(我说通常,因为我认为程序员可能会轻率地使用封装)。

这是实现或标准措辞上的一处故障吗?

不是故障。语言被指定为这样工作。

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

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


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

不是未定义行为。 Base 有该成员。


-1

仅仅是为了补充答案并且更深入地阅读你的话语之间的恐惧。如果你把访问说明符看作是“法律”,限制你不要做“坏事”,我认为你没有理解到重点。publicprotectedprivateconst……都是 C++ 系统中的一部分,这是一个巨大的优势。没有这些东西的语言可能有很多优点,但是当你构建大型系统时,这些东西是真正的资产。

话虽如此:我认为能够绕过几乎所有提供给你的安全网是一件好事。只要记住“可能”并不意味着“好”。这就是为什么它永远不应该是“容易”的原因。但是对于其他方面——由你决定。你是架构师。

多年前,我可以简单地这样做(在某些环境下可能仍然有效):

#define private public

对于“敌对”的外部头文件非常有帮助。这是一个好的实践吗?你怎么看?但有时你的选择是有限的。

所以,是的,你展示的东西有点违反了系统。但是嘿,什么阻止你派生并公开引用成员呢?如果可怕的维护问题让你感到兴奋-那就尽管去做吧!


我觉得第一段有点令人困惑。如果不是让你避免做“坏事”的方式,那么访问限定符还有什么作用呢?据我所知,在一个正确的程序中,你可以把所有私有成员变量都改成公有的,它依然是正确的。 - 463035818_is_not_a_number
我把所有这些看作是工具。类型安全、常量正确性、访问说明符 - 我们知道我们可以不用它们(选择你的语言),但我们可以选择使用这些工具。每个决定都有优点和缺点。你的意思是说,任何有选择性和故意关闭的选择都会一直是不好的吗?你曾经使用过转换吗? - Bert Bril

-2

基本上你所做的是欺騙編譯器,這應該可以運作。我總是看到這種問題,有時人們會得到不好的結果,有時則能成功運行,這取決於它如何轉換成彙編程式碼。

我記得曾經看到一個例子,其中一個整數有一個“const”關鍵字,但是通過一些詭計,那個人能夠更改其值並成功地繞過了編譯器的感知。結果是:對於一個簡單的數學操作得出錯誤的值。原因很簡單:在x86中,組合語言確實區分常量和變量,因為某些指令包含常量操作碼。因此,由於編譯器“認為”它是一個常量,它將把它當作常量處理,以一種被優化的方式與錯誤的CPU指令處理,然後你就會得到錯誤的結果。

換句話說:編譯器將嘗試強制執行所有可以執行的規則,但你可能最終會欺騙它,如果你不知道你在做什麼,你可能會得到錯誤的結果,因此最好只有在你知道自己在做什麼時才進行此類操作。

在你的情况下,指针&Derived::value可以通过从类开始位置算起的字节数来计算对象。这基本上是编译器访问它的方式,所以编译器:

  1. 没有权限问题,因为你在编译时通过derived访问了value
  2. 可以这样做,因为你获取的是一个与derived(显然也包括base)结构相同的对象中的字节偏移量。

所以,你并没有违反任何规则。你成功地绕过了编译规则。根据你附加的链接中描述的原因,你不应该这样做,因为它破坏了面向对象编程的封装性,但是,如果你知道自己在做什么...那就无妨。


3
我怀疑OP并非在询问实际情况。 - Passer By
6
很努力的尝试。但这个回答很长,却没有回答问题。因为我认为提问者想知道这个问题的正式用语是什么。 - StoryTeller - Unslander Monica
我试图解释为什么这个有效...希望它有所帮助。 - The Quantum Physicist
5
我认为问题在于你的答案适用于特定的架构和/或实现,但 OP 希望得到一个通用的答案,即适用于 C++ 抽象机器的答案。 - Rakete1111
6
当发布者坚持自己的立场时,通常会更容易。投票的主要标准是“有用性”。虽然一些涉及相关方面的答案可能是有用的,但这个答案并不是。正如Rakete1111所说,你关注的是OP并不太在意的方面。 - StoryTeller - Unslander Monica
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接