22

我有以下课程:

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;

我知道这是不好的做法。但只是出于好奇:这能保证有效吗?它是定义的行为吗?

额外的问题:你曾经使用过这么丑陋的黑客吗?

编辑:只是为了吓唬更少的人:我不打算在真正的代码中真正做到这一点。我是在想 ;)

4

6 回答 6

21

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

编辑:重新审视这个旧答案,我意识到我错过了一个相当明显的观点:每个结构定义都只有一个数据成员。成员函数的顺序无关紧要,因为它们对类的布局没有贡献。您可能会发现两个数据成员都被保证在同一个地方,尽管我对标准的了解还不足以肯定地说。

但!您不能取消引用reinterpret_cast不相关类型之间的 ing 结果。还是UB。至少,这是我对http://en.cppreference.com/w/cpp/language/reinterpret_cast的阅读,这确实是一本难读的书。

于 2010-05-14T13:25:33.300 回答
9

由于Marcelo指出的原因,这是未定义的行为。但有时在集成无法修改的外部代码时,您需要求助于此类事情。一种更简单的方法(以及同样未定义的行为)是:

#define private public
#include "BritneySpears.h"
于 2010-05-14T13:30:23.587 回答
4

您可能无法修改 .h 的库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++。

于 2010-05-14T15:27:55.063 回答
2

@Marcelo 说得对:成员的顺序在不同的访问级别上是未定义的。

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

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++。

于 2010-05-14T13:37:04.803 回答
2

您的代码存在问题,答案有下划线。问题来自对值的排序。

但是你几乎在那里:

class AshtonKutcher
{
public:

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

private:
  int m_value;
};

现在,您拥有完全相同的布局,因为您具有相同的属性、以相同的顺序声明并具有相同的访问权限......并且两个对象都没有虚拟表。

因此,诀窍不是更改访问级别,而是添加一个方法:)

当然,除非我错过了什么。

我是否准确地说这是一场维护噩梦?

于 2010-05-14T16:51:03.500 回答
1

通常应避免使用 reinterpret_cast 并且不能保证提供可移植的结果

另外,为什么要更改私人会员?您可以将原始类包装在一个新类中(更喜欢组合而不是继承)并根据需要处理 getValue 方法。

于 2010-05-14T13:27:25.060 回答