修改vfptr指向的元素
class Animal
{
protected:
string _name;
public:
Animal(string name) :_name(name) {}
virtual void bark() = 0;
};
class Cat :public Animal
{
public:
Cat(string name) :Animal(name) {}
void bark() { cout << _name << ":喵喵" << endl; }
};
class Dog :public Animal
{
public:
Dog(string name) :Animal(name) {}
void bark() { cout << _name << ":汪汪" << endl; }
};
int main()
{
Animal* p1 = new Cat("加菲猫"); // 父类指针指向在堆上创建的子类对象地址
Animal* p2 = new Dog("哈士奇");
int* ptr1 = (int*)p1; // 强转为int类型指针(ptr1 = p1)
int* ptr2 = (int*)p2; // ptr1指向Cat对象,ptr2指向Dog对象
// 此时ptr1 2所指向的地址就是子类对象vfptr所指向的地址,根据动态绑定
// ptr1指向vftable的Cat::bark()(覆盖了),ptr2指向Dog::bark()
int tmp = ptr1[0]; // 因为是int类型,ptr1[0]访问Cat指针vfptr的前四个字节
ptr1[0] = ptr2[0];
ptr2[0] = tmp;
// 现在将二者vfptr前四个字节保存的地址交换,Cat指向Dog,Dog指向Cat
// 所以ptr1指向vftable的Dog::bark(),ptr2指向Cat::bark()
p1->bark(); // 虽然p1指向Cat的vfptr,但保存的是Dog的bark()
p2->bark(); // 输出加菲猫:汪汪 哈士奇:喵喵
// 上面有点啰嗦,简单地说:就是交换了两者vfptr指向的首地址元素,导致输出错乱
delete p1, p2;
return 0;
}
编译期间压栈与访问控制权限问题
/*
如果new了一个父类指针p
1要注意delete p
2如果父类中没有实现析构时(使用默认析构时)不用担心
但在父类中实现了自己的析构时必须将其设置为虚析构
fun1():
父类指针p调用子类方法时的顺序:p->derive.vfptr->derive.vftable->derive::fun1()
但在编译期间,会先编译base的fun1(),并将i赋值10压栈(压入栈就不会再变动了)
而多态是在运行期实现的,执行fun1()时确实找到了子类的fun1(),但出栈的是之前压栈的10而不是20
解决方法:
1不加形参变量
2如果非要加,也要写成一样的值
fun2():
可以正确被输出,因为在编译期调用的是父类public的fun2()
而在运行期才会调用子类private的fun2()
说明:public,private等限制的是编译期间是否合法,在运行期间不看是否private,都可以访问
*/
class base
{
public:
virtual void fun1(int i = 10) // 编译期将10压栈
{
cout << "父类的值:" << i << endl;
}
virtual void fun2()
{
cout << "父类的fun2()" << endl;
}
private:
//virtual void fun2() // 编译期间的private就不能通过了
//{
// cout << "父类的fun2()" << endl;
//}
};
class derive :public base
{
public:
void fun1(int i = 20) // 这个20不会被执行
{
cout << "子类的值:" << i << endl;
}
private:
void fun2() // 运行时可以访问private成员方法
{
cout << "父类的fun2()" << endl;
}
};
int main()
{
base* p = new derive();
p->fun1(); // 输出 子类的值:10
p->fun2(); // 可以正确输出,因为编译的是父类的public方法
delete p;
derive* p1 = new derive();
//p1->fun2(); // 编译期间不可访问private的方法
delete p1;
return 0;
}