私有成员黑客行为是否被定义为合法行为?

23

我有以下的类:

class BritneySpears
{
  public:

    int getValue() { return m_value; };

  private:

    int m_value;
};

这是一个外部库(我无法更改)。显然,我无法更改m_value的值,只能读取它。即使从BritneySpears导出也不起作用。

如果我定义以下类:

class AshtonKutcher
{
  public:

    int getValue() { return m_value; };

  public:

    int m_value;
};

然后执行:

BritneySpears b;

// Here comes the ugly hack
AshtonKutcher* a = reinterpret_cast<AshtonKutcher*>(&b);
a->m_value = 17;

// Print out the value
std::cout << b.getValue() << std::endl;

我知道这是不好的实践。但只出于好奇:这个是有保证可以工作吗?它是定义好的行为吗?

额外问题:你曾经必须使用过这样丑陋的hack吗?

编辑:只是为了让更少的人感到害怕:我并不想在真实的代码中实际执行这个。我只是好奇而已 ;)


14
只需让BritneySpears::getValue()AshtonKutcher::getValue()简单地返回0,就可以大大优化此代码。 - Syntactic
1
@ereOn:哦,我不够聪明来指出C++代码中的错误。我指的是如果这些类正确地模拟了真实的布兰妮·斯皮尔斯和阿什顿·库彻,它们将必须具有值0。 - Syntactic
6
你为什么想要接触布兰妮·斯皮尔斯的私处?(抱歉,我真的很努力了,但是我就是忍不住。) - sbi
3
在使用未定义行为的示例中使用BritneySpears,我给予赞赏(+1)。请注意,我的翻译尽量保持原意不变,同时使文本更易懂。 - Hugh Brackett
1
在同一句话中提到布兰妮的私处和“丑陋的黑客”真是太棒了。 - György Andrasek
显示剩余7条评论
6个回答

21

这是未定义行为。每个访问限定符部分内的成员按它们出现的顺序排列,但在访问限定符之间并没有这样的保证。例如,如果编译器选择在所有公共成员之前放置所有私有成员,则上面两个类将具有不同的布局。

编辑:重新审视这个旧答案,我意识到一个非常明显的问题:结构体定义正好有一个数据成员。成员函数的顺序无关紧要,因为它们不会对类的布局产生影响。你可能会发现两个数据成员都被保证处于相同的位置,尽管我不确定标准是否充分。

但是!不能解引用使用reinterpret_cast转换的不相关类型的结果。这仍然是未定义行为。至少根据http://en.cppreference.com/w/cpp/language/reinterpret_cast ,这是一个复杂难懂的阅读。


1
如果 BritneySpears 的每个成员都是私有的,而 AshtonKutcher 的每个成员都是公共的,那么顺序会变得有保障吗? - ereOn
1
@ereOn:只有当它们都在单个访问限定符部分中时才有效。拥有两个私有部分仍会导致未指定的内存布局。 - Marcelo Cantos
2
像这样的东西,确切了解的唯一方法是阅读和理解标准。 - KeithB
1
即使每个类的成员在排版上完全相同,如果它们使用不同的编译器选项或不同的编译器进行编译,仍然无法保证实际的内存布局。 - David R Tribble
@KeithB 我的英语水平还不够好,无法在不严重头痛的情况下阅读这样的标准。 - ereOn
显示剩余2条评论

9

由于Marcelo所指出的原因,这是未定义的行为。但有时,当集成外部代码且无法修改时,您需要采用这种方式。更简单的方法(同样是未定义的行为)如下:

#define private public
#include "BritneySpears.h"

5
我们可以拥有没有"private"标签的私有成员,因此这可能行不通 ;) - Nick Dandoulakis
3
@Nick: #define class struct。并非我会做这样的事情。 - Mike Seymour
1
@Tyler,这是很好的信息。不过,如果你混合使用编译器,那么总体上你不是很困难吗?毕竟,C++没有定义ABI。 - Michael Kristofik
添加 #define protected public。 - Demi
1
你不能使用 #define 宏定义 C++ 关键字,因此 #define private public 不是合法的 C++ 代码。 - JarkkoL
显示剩余4条评论

4

您可能无法修改 BritneySpears 的库,但您应该能够修改 .h 头文件。如果可以的话,您可以将 AshtonKutcher 设为 BritneySpears 的好友:

class BritneySpears 
{
    friend class AshtonKutcher;
  public: 

    int getValue() { return m_value; }; 

  private: 

    int m_value; 
}; 

class AshtonKutcher 
{ 
  public: 

    int getValue(const BritneySpears & ref) { return ref.m_value; }; 
}; 

我不太赞成这个技巧,而且我自己也从未尝试过,但它应该是符合C++的法律规定的。


1
谁会想和布兰妮·斯皮尔斯交朋友? - fredoverflow

2

@Marcelo是正确的:在不同的访问级别中,成员的顺序是未定义的。

但请考虑以下代码;在这里,AshtonKutcherBritneySpears具有完全相同的布局:

class AshtonKutcher
{
  public:
    int getValue() { return m_value; };
    friend void setValue(AshtonKutcher&, int);

  private:
    int m_value;
};

void setValue(AshtonKutcher& ac, int value) {
    ac.m_Value = value;
}

我相信这可能是有效的C++代码。


好的,我们不能保证编译器会以相同的顺序为两个不同的类排序不同的部分。嗯,我看不出哪种实现会像那样行事,但还是要注意。 - ereOn

2

你的代码存在问题,答案中已经标出。问题出在对值进行排序。

不过你已经接近成功了:

class AshtonKutcher
{
public:

  int getValue() const { return m_value; }
  int& getValue() { return m_value; }

private:
  int m_value;
};

现在,你拥有完全相同的布局,因为你拥有相同的属性,以相同的顺序声明,并具有相同的访问权限...并且两个对象都没有虚表。
因此,诀窍不是改变访问级别,而是添加一个方法 :)
当然,除非我漏掉了什么。
我是否明确这是维护噩梦?

1

应尽量避免使用reinterpret_cast,因为它不能保证给出可移植的结果

此外,为什么要更改私有成员?您可以在新类中包装原始类(优先使用组合而非继承),并根据需要处理getValue方法。


嗯,我只是在想。我并不打算在任何真正的代码中使用这个 ;) - ereOn
由于 getValue 方法不是虚函数,因此您将无法通过扩展类来更改其在现有代码中的行为(所有现有基本级别引用都将调用基础方法,并且派生类中 --实际上并未覆盖-- 的方法永远不会被调用)。 - David Rodríguez - dribeas

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