注:visual studio复制当前行粘贴到下一行: CTRL+D
杂项
调用子类重写的虚函数(带默认参数),但参数用的是基类的虚函数中的默认参数:
这是由于参数是在编译时压入
试题一
交换两个基类指针指向的对象的vfptr
造成的运行结果变化
class Animal
{
public:
Animal(string name) :_name(name) {}
// 纯虚函数
virtual void bark() = 0;
protected:
string _name;
};
// 以下是动物实体类
class Cat : public Animal
{
public:
Cat(string name) :Animal(name) {}
void bark() { cout << _name << " bark: miao miao!" << endl; }
};
class Dog : public Animal
{
public:
Dog(string name) :Animal(name) {}
void bark() { cout << _name << " bark: wang wang!" << endl; }
};
int main()
{
Animal* p1 = new Cat("加菲猫");
Animal* p2 = new Dog("二哈");
int* p11 = (int*)p1;
int* p22 = (int*)p2;
int temp = p11[0]; // p11[0]访问Cat的前4个字节,即 指向Cat的vftable的vfptr
p11[0] = p22[0]; // p22[0]访问Dog的前4个字节,即 指向Dog的vftable的vfptr
p22[0] = temp;
// 上面代码相当于交换了虚函数表指针
// 导致下面调用虚函数时就发生了非预期的情况
p1->bark(); // 实际调用Dog::bark()
p2->bark(); // 实际调用Cat::bark()
delete p1;
delete p2;
return 0;
}
试题二
有如下代码
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
virtual void show(int i = 10)
{
cout << "call Base::show i:" << i << endl;
}
};
class Derive : public Base
{
public:
void show(int i = 20)
{
cout << "call Derive::show i:" << i << endl;
}
};
int main()
{
Base* p = new Derive();
p->show();
delete p;
return 0;
}
// 输出结果
// call Derive::show i:10
发现调用的是派生类覆盖的函数,确实是动态绑定,但使用的参数却是基类虚函数的默认参数,而不是派生类重写的虚函数的默认值。
原因是函数调用参数压栈是在编译时期,具体call
函数可以在运行时,p->show()
汇编指令大致如下:
push 0Ah # 压入基类的默认实参10
mov eax, dword ptr[p]
mov ecx, dword ptr[eax]
call ecx
试题三
利用多态能调用到派生类private
成员函数
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
virtual void show(int i = 10)
{
cout << "call Base::show " << endl;
}
};
class Derive : public Base
{
private:
void show(int i = 20)
{
cout << "call Derive::show" << endl;
}
};
int main()
{
Base* p = new Derive();
// 成员方法限定符是在【编译时】检查,由于编译时通过p检查的是Base::show(),没有问题
// 而运行时调用子类覆盖的成员这不影响函数执行
p->show(); // 最终调用到Derive::show(),是在运行时期确定
delete p;
return 0;
}
// 输出结果:call Derive::show
如果把基类的虚函数访问权限设置为private
,则在编译阶段无法通过
试题四
说明下面代码段一和二是否正确执行,说明理由
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
Base()
{
cout << "call Base()" << endl;
clear();
}
// !!!!!!!!!!!!!!!!!!!!!!!!
void clear() { memset(this, 0, sizeof(*this)); }
virtual void show()
{
cout << "call Base::show()" << endl;
}
};
class Derive : public Base
{
public:
Derive()
{
cout << "call Derive()" << endl;
}
void show()
{
cout << "call Derive::show()" << endl;
}
};
int main()
{
// 代码段一
Base* pb1 = new Base();
pb1->show();
delete pb1;
// 代码段二
/*Base* pb2 = new Derive();
pb2->show();
delete pb2;*/
return 0;
}
解答:
代码段一执行失败
因为new Base()
调用构造函数时,进入第一行代码前会生成vfptr
(就是将vftable
地址写入其中),指向Base
类对应的虚函数表,但是在构造函数中后面又调用clear
函数将内存清0,导致vfptr被置零,随后调用 pb1->show();
时会使用vfptr
则会造成非法访问,运行时提示:
引发了异常: 读取访问权限冲突。
pb1->**** 是 nullptr
。
代码段二执行成功
执行 new Derive();
时先调用基类构造,虽然在构造函数中也会使vfptr
被清零,但是随后调用派生类构造会将子类的vftable地址赋值给vfptr,所以导致最后通过 pb2->show();
访问vfptr
是没问题的