私有成员只是一块内存而已,拿到地址就照样可以操作(对象public private的保护是在编译时进行的):
#include<iostream>
using namespace std;
class A{
public:
int a;
void show()
{
cout<<c<<endl;
}
private:
int c;
};
int main()
{
A a;
int *p=(int *)&a;
*(p+1)=3;//直接对私有成员c操作 this指针子在&A-1处
a.show();
}
但是:
第一,这种方法无法跨平台跨实现,char及int类型在不同的平台和编译器实现中的长度都可能是不一样的;
第二,没有考虑字对齐问题,在内存中,value成员一般不会紧接着排布在ch之后,而是中间间开几个字节,最后将int类型对齐到另一个位置,比如4的倍数的地址上;而更糟糕的是,字对齐不仅跟平台相关,还跟预编译指令,甚至编译选项都会有关。
方法二:使用union
#include <iostream>
using namespace std;
class A{
private:
int a;
};
union test
{
A a;
int b;
};
int main()
{
test t;
t.b=1;//联合体中的a已被修改
}
#include <iostream>
using namespace std;
class A{
private:
int c;
int a;//修改a
};
class B{
public:
int c;
int a;
};
union test
{
A a;
B b;
};
int main()
{
test t;
t.b.a=1;//联合体中的a已被修改
}
其他情况:
#include <iostream>
using namespace std;
class A{
private:
int c;
public:
int d;
protected:
int e;
public:
int as;
private:
int bs;
protected:
int ddd;
private :
int a;
};
class B{
public:
int c;
int d;
int e;
int as;
int bs;
int ddd;
int a;
};
union test
{
A a;
B b;
};
int main()
{
test t;
t.b.a=2;//方法1联合体中的a已被修改
A a;
((B*)&a)->a=1;//方法2
*((int *)(&a)+6)=1;//方法3
}
复杂情况2:
#include <iostream>
using namespace std;
class A { //对于更复杂的情况,增加了一个虚函数表
public:
int get_value() { return value; }
private:
char ch;
int value;
public:
int a;
double b;
virtual ~A() {}
string e;
private:
short d;
};
class B {
public:
int get_value() { return value; }
// virtual ~B() {}
private:
char ch;
int value;
public:
int a;
double b;
string e;
private:
short d;
virtual void show(){}
};
union test//编译器不允许我们的union成员有构造函数,析构函数/拷贝构造函数/赋值运算符/虚函数的类成员变量,要使用c风格的struct成员,此时无法使用联合体
{
// A a;
// B b;
};
int main()
{
// test t;
// t.b.a=2;//联合体中的a已被修改
A a;
((B*)&a)->value=1;//ok
((int )(&a)+3)=1;//虚表占了1个字节,但是有字节对齐需要考虑
}
另外:
#include <iostream>
using namespace std;
class A { //对于更复杂的情况,增加了一个虚函数表 ,且有其他类在value前,难以计算大小
public:
int get_value() { return value; }
string c;
private:
char ch;
int value;
public:
int a;
double b;
virtual ~A() {}
string e;
private:
short d;
};
class B {
public:
void change(){value=1;}//
int get_value() { return value; }
string c;
private:
char ch;
int value;
public:
int a;
double b;
virtual ~B() {}
string e;
private:
short d;
};
int main()
{
A a;
a.e = new char[10];
((B*)&a)->value=1;//更改B的value属性为public 不一定总成立
((B*)&a)->change();//该方法始终成立 增加public函数更改value值
// *((int *)(&a)+3)=1;//虚表占了1个字节,但是有字节对齐需要考虑
}
这次不仅成员多了许多,有string类型的成员(须include <string>
),还弄出个虚析构函数来(我们都知道拥有虚函数的类会导致其实例中多一个虚表指针)。但后面会看到,虚函数对我们讨论的问题影响不大,我们加上它只是想证明:只要方法足够好,不怕对象更复杂。
那上面的模具办法问题出在哪里呢?为什么不能同样再搞一个类,把那个value改为public的,然后用它来“套住”原来对象中value成员呢?
原因是C++语言只保证类中同一个access section(即从一个访问权限修饰符public/private/protected到另一个修饰符之间的部分)中定义的非静态成员变量会按照声明时的顺序分布的内存中,但并不保证跨越了不同access section的所有成员变量都在内存中按声明时的顺序存放,某种编译器完全有可能把所有的private块都合成一块,甚至整个给扔到所有protected成员的后边去(虽然VC并没这么做)。
换句话说:改掉了一个成员的访问权限,就可能改变了对象的内存布局。于是,改变了的模子也就不再能够套住相应位置上的成员。
但办法还是有,只需要将原来的改进一下:
在现有的C++对象模型中,为类增加一个非虚成员函数,不会改变对象的内存布局,我们可以利用这一点来写一个TestTwin:
增加的虚函数纯粹是个障眼物而已,它跟我们采用的方法几乎没有丝毫联系,所以也就丝毫不用担心虚函数对内存分布的影响会影响到这个方法的正确性。但被它一搞,那个使用联合体的方法这一次还真是不管用了,因为有了析构函数的类不能再放进联合体中了——否则当联合体实例的生命周期结束时,析构谁呢?