专业编程基础技术教程

网站首页 > 基础教程 正文

C++ 虚函数 实例学习 简单易懂 c++虚函数的使用

ccvgpt 2024-11-12 09:55:17 基础教程 5 ℃

大家好,这篇文章带大家深入解析一下C++的虚函数。

这里需要给大家带入一个场景,来使大家更容易理解一些。

C++ 虚函数 实例学习 简单易懂 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++的虚函数,在实际工作中解决问题。

最近发表
标签列表