网站首页 > 基础教程 正文
大家好,这篇文章带大家深入解析一下C++的虚函数。
这里需要给大家带入一个场景,来使大家更容易理解一些。
开场
这个场景就是我们需要在程序运行时,来替换一个虚函数表中的虚函数。
大家可能要问,为什么要这么做?直接改源代码不好吗?
我要说的是,如果这一部分程序是第三方提供的,而你没有源代码怎么办?
虽然说,这么做从软件设计角度来说不好,但是也许有一天能帮你解决实际工作中的问题。而且,你也可以在面试中展示一下你对C++语言的理解,面试官会觉得你技术很高啊。
那么咱们就开始吧!
首先,我们来看一段代码:
class Base
{
public:
virtual ~Base();
virtual void buggyFunction();
};
不是
class Derived : public Base
{
public:
// This function fails sometimes.
void buggyFunction() override;
};
这个buggyFunction()时不时的总是出错,假设我们知道了在哪些条件下这个函数会出错,然后我们的代码,在运行时检查这些条件,一旦符合条件,就把它从虚函数表中替换掉。
一些技术背景
C++标准并没有要求编译器如何来实现虚函数这一功能。然而,在实践当中,编译器会生成一个虚函数表,然后在类的实现中,把这个虚函数表的指针作为这个类的第一个成员。
多重继承情况又会不一样,这里大家先别钻牛角尖。
大家定义了下面这样一个C++类:
class A
{
public:
virtual ~A();
virtual int func(int);
int member;
};
由于C++标准也没有要求虚函数表怎样来实现,不同的编译器也会有不同的实现方法。
例如,GCC编译器就会下面的代码这样来实现:
struct A_vtable
{
void* type_info; // Pointer to type information
void (*destructor)(A*); // Function pointer
int (*func)(A*,int); // Function pointer
};
虚函数表,并不一定是一个表结构。
虚函数的指针按照他们在class A中声明的顺序来排列。
这里的A_vtable解释了一个秘密,C++成员函数只不过是一个稍微复杂一点的普通函数,他们只不过多一个类的指针作为第一个参数而已。如果大家用过Python,就会秒懂了~
其实,C++成员函数的语法还有好多可以吐槽的地方,以后咱们再说。
// 这是你写的成员函数的实现
int A::func(int param)
{
return param;
}
// 这是编译器帮你生成的对应的代码
// 实际情况下,编译器会生成一个长长的很难记的名字
int A_func(A* this, int param)
{
return param;
}
class A的所有实例会共享一个虚函数表,编译器会把它实现为一个静态全局变量,然后编译器还会帮你把类成员函数的指针赋值给它,还有就是生成的类型信息。
例如:
const A_vtable A_vtable_instance
{
&A_type_info, // We'll skip this now
&A_destructor, // Pointer to an ordinary function
&A_func // Ditto
};
所以当你生成了一个class A的实例,你实际上创建了下面这样的一个结构。
struct class_A
{
A_vtable* vtable;
int member;
};
编译器会帮助你给虚函数表指针赋值。
// 你写的代码
A a;
// 编译器实际生成的代码
class_A a;
a.vtable = &A_vtable_instance;
如果你定义了一个class B,B继承自A。
编译器做的工作大概就像下面这样:
// You say
class B : public A
{
public:
int func(int) override;
virtual void func2() const; // A new virtual function
};
int B::func(int) {}
void B::func2() const {}
// The compiler generates
struct B_vtable
{
void* type_info;
void (*destructor)(B*);
int (*func)(A*,int);
void (*func2)(const B*);
};
// Auto-generated default destructor.
void B_destructor(B* b)
{
A_destructor((A*)b);
}
int B_func(B*, int) {}
// Member function qualifiers are actually qualifiers
// of the this pointer.
void B_func2(const B*) {}
// Note that the first three members have the same
// layout as A_vtable, which means we can safely access
// a B_vtable pointer as an A_vtable pointer.
const B_vtable B_vtable_instance
{
&B_type_info,
&B_destructor,
&B_func,
&B_func2
};
struct class_B
{
B_vtable* vtable;
int member;
};
当你在调用一个A中的虚函数时,编译器实际上是去虚函数表中查找它。
// You say
B b;
A* a = &b;
a->func(1);
// You get
class_B b;
b.vtable = &B_vtable_instance;
class_A* a = &b;
a->vtable->func(a, 1);
解决问题的代码
既然我们知道C++编译器如何工作了,我们只要找到需要替换虚函数的实例,然后把指向buggyFunction()的指针替换成我们想要执行的函数,就行了。
// hack.cc
#include <iostream>
// This is the published interface we work with.
class Base
{
public:
virtual ~Base()
{
std::cout << "Base::~Base()\n";
}
virtual void buggyFunction()
{
std::cout << "Base::buggyFunction()\n";
}
};
// This is class is implemented in a closed library.
class Derived : public Base
{
public:
~Derived()
{
std::cout << "Derived::~Derived()\n";
}
void buggyFunction() override
{
// This sometimes fails.
std::cout << "Derived::buggyFunction()\n";
Base::buggyFunction();
}
};
static void fixedFunction(Base* thisPointer)
{
std::cout << "fixedFunction()\n";
// Bypass virtual function resolution.
thisPointer->Base::buggyFunction();
}
// This is GCC's vtable layout for Base.
struct FixedVTable
{
void* typeInfo = nullptr;
void (*destructor)(Base*) = nullptr;
// The fix.
void (*buggyFunction)(Base*) = &fixedFunction;
};
FixedVTable fixedVTableInstance;
static void overwrite(Base* b)
{
// b actually points to a vtable pointer.
auto pVTable = reinterpret_cast<FixedVTable**>(b);
// Copy retained entries from actual vtable.
fixedVTableInstance.typeInfo = (*pVTable)->typeInfo;
fixedVTableInstance.destructor = (*pVTable)->destructor;
// Replace vtable pointer in this instance.
*pVTable = &fixedVTableInstance;
}
int main()
{
Derived d;
Base* b = &d;
b->buggyFunction();
overwrite(b);
b->buggyFunction();
}
运行结果:
Derived::buggyFunction()
Base::buggyFunction()
fixedFunction()
Base::buggyFunction()
Derived::~Derived()
Base::~Base()
fixedFunction在运行时替代了buggyFunction。
希望这篇文章能够帮助大家更好的理解C++的虚函数,在实际工作中解决问题。
- 上一篇: C++里面的虚析构函数 虚构造函数与虚析构函数
- 下一篇: c++的面试总结 c++面试知识点
猜你喜欢
- 2024-11-12 金三银四不跳槽更待何时?安卓开发1年字节5面面经,已成功上岸
- 2024-11-12 C++要学到什么程度才能找到实习? c++学完学什么
- 2024-11-12 C++基础语法梳理:inline 内联函数!虚函数可以是内联函数吗?
- 2024-11-12 C++基类中虚析构函数 c++ 虚析构函数
- 2024-11-12 C和C++代码精粹:C语言和C++有什么区别么?
- 2024-11-12 3个面试C++开发岗位的高频笔试题 c++软件开发面试
- 2024-11-12 一文在手,"类间关系"不再困惑
- 2024-11-12 c++的面试总结 c++面试知识点
- 2024-11-12 C++里面的虚析构函数 虚构造函数与虚析构函数
- 2024-11-12 C++虚函数会导致性能开销大? c++虚函数使用
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)