========================================
class a { private: int k; };
要求不用友元,不在这个类里添加任何代码,去访问成员变量k。 开始讨论之前不得不说,这道题做为一个题目存在逻辑上的重大缺陷:“不给类增加一行代码”,我实在想不出如何在验正解题人所提供方案的正确性。只有一个private,难道用解题人所提供的读出方案来验证他自己所提供的写入方案?你用你的方法读出来,然后告诉我那就是你用你的方法写进去的值——那能让人信服吗?所以我决定还是把问题改一改,并稍微具体化如下:
要求不用友元,不在这个类里添加任何代码,把成员变量k的值改为100,结果自然是通过公共成员函数get_value来验证。
class Test {
public:
int get_value() { return value; }
private:
int value;
};
“不在类里添加任何代码”,除了 也就是说,它动用了C++语言中最“强”的指针转换方式(说它最强,是因为没有什么指针之间他不能转换的)。其实我们完全可以做得更“文明”一点,方法是再定义一个联合体,比如:
union TestInt {
Test t;
int i;
};
然后再: TestInt ti;
ti.i = 100;
cout << ti.t.get_value() << endl;
同样达到了目的,但实质上依据的机理跟上面的指针转换是一致的。 这种方法就不灵了。
class Test {
public:
int get_value() { return value; }
private:
char ch;
int value;
};
当然,你可以手工算,认为char占一个字节,于是会试图取对象地址再加1得到成员value的地址。但第一,这种方法无法不跨平台跨实现,char及int类型在不同的平台和编译器实现中的长度都可能是不一样的;第二,没有考虑字对齐问题,在内存中,value成员一般不会紧接着排布在ch之后,而是中间间开几个字节,最后将int类型对齐到另一个位置,比如4的倍数的地址上;而更糟糕的是,字对齐不仅跟平台相关,还跟预编译指令,甚至编译选项都会有关。所以,这种手工计算的方式还是放弃了吧。
有朋友提到了使用一种宏求出value成员相对于整个对象起始地址的偏移量,即定义一个宏: class TestTwin {
public:
int get_value() { return value; }
public:
char ch;
int value;
};
于是,这个TestTwin类跟原来的Test类在内存布局上不会有什么不同,通过指针转换,很容易借助于它来修改Test类对象的value成员: Test t;
TestTwin* p = reinterpret_cast<TestTwin*>(&t);
p->value = 100;
cout << t.get_value() << endl;
如果你不熟悉C++的显式指针转换方式:reinterpret_cast,在这里可以认为它相当于C风格的: 还有,厌恶指针操作的朋友仍可采用前面介绍的联合体方法来运用这个模具类,只是这次定义的联合体是这样:
union TestTestTwin {
Test t;
TestTwin tw;
};
而程序是这样:
TestTestTwin ttw;
ttw.tw.value = 100;
cout << ttw.t.get_value() << endl;
问题都解决了吗?如果类更复杂一些,会不会还有局限性呢?我们再把类改一改:
class Test {
public:
int get_value() { return value; }
~Test() {}
private:
char ch;
int value;
public:
int a;
double b;
protected:
string e;
private:
short d;
};
这次不仅成员多了许多,有string类型的成员(须include <string>),还弄出个虚析构函数来(我们都知道拥有虚函数的类会导致其实例中多一个虚表指针)。但后面会看到,虚函数对我们讨论的问题影响不大,我们加上它只是想证明:只要方法足够好,不怕对象更复杂。 在现有的C++对象模型中,为类增加一个非虚成员函数,不会改变对象的内存布局,我们可以利用这一点来写一个TestTwin:
class TestTwin {
public:
int get_value() { return value; }
void set_value(int v) { value = v; }
~TestTwin() {}
private:
char ch;
int value;
public:
int a;
double b;
protected:
float e;
private:
short d;
};
这个模具跟原来的Test类也是只有一点不同:增加了一个公共的,非虚的set_value方法,用来给私有成员value赋值。于是,程序可以这么写:
Test t;
reinterpret_cast<TestTwin*>(&t)->set_value(100);
cout << t.get_value() << endl;
验证通过。 最后,不行不承认,“增加一个非虚成员函数,不会改变对象的内存布局”这句话也无法从C++标准中得到直接支持,只是对于目前大多数编译器来说,这都是没问题的。因为这种“让类的每个实例拥有一份独立的成员变量,而类的所有实例共享一份成员函数”的C++对象模型是C++之父Bjarne Stroustrup先生本人所提出的,其时间、空间效率都很好地符合了C++语言的设计初衷,不仅现代C++编译器没有不这么做的,就连Java/C#编译器也都这么做。所以,也算是个“相对真理”了。
转自:http://www.cppblog.com/andxie99/archive/2006/10/24/14090.aspx