我有以下课程:
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;
我知道这是糟糕练习。但出于好奇:这是否可以保证有效?是否定义了行为?
奖金问题:你有没有必要使用这样丑陋的黑客?
答案 0 :(得分:21)
这是未定义的行为。每个访问限定符部分中的成员都保证按其出现的顺序排列,但是在acccess限定符之间没有这样的保证。例如,如果编译器选择将所有私有成员放在所有公共成员之前,则上述两个类将具有不同的布局。
编辑:重新回答这个旧答案,我意识到我错过了一个相当明显的观点:结构定义每个都只有一个数据成员。成员函数的顺序无关紧要,因为它们不会影响类的布局。你可能会发现两个数据成员都保证在同一个地方,但我不太清楚这个标准是否足以说明。
但是!您无法取消引用不相关类型之间reinterpret_cast
的结果。它仍然是UB。至少,这是我对http://en.cppreference.com/w/cpp/language/reinterpret_cast的阅读,这确实是一个粗略的阅读。
答案 1 :(得分:9)
这是未定义的行为,原因Marcelo指出。但有时你需要在集成无法修改的外部代码时采用这些方法。一种更简单的方法(以及同样未定义的行为)是:
#define private public
#include "BritneySpears.h"
答案 2 :(得分: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 ++。
答案 3 :(得分: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 ++。
答案 4 :(得分:2)
您的代码存在问题,答案下划线。问题来自订购价值。
然而你几乎就在那里:
class AshtonKutcher
{
public:
int getValue() const { return m_value; }
int& getValue() { return m_value; }
private:
int m_value;
};
现在,您具有完全相同的布局,因为您具有相同的属性,以相同的顺序声明,并具有相同的访问权限......并且两个对象都没有虚拟表。
因此,诀窍不是改变访问级别,而是添加方法:)
当然,除非我错过了什么。
我确切认为这是维护噩梦吗?
答案 5 :(得分:1)
通常应避免使用reinterpret_cast,并且无法保证portable results。
另外,为什么要更改私人会员?您可以将原始类包装在一个新类中(更喜欢组合而不是继承)并根据需要处理getValue方法。