RT,面试中经常被问到,那么熟悉虚函数表的实际意义是啥?为什么面试官特别喜欢考这个点, GET这个点后能干些什么呢?不懂这点会有啥影响呢?


提供 debug 思路。例如调用虚函数 core 了。


节省调试时间,尤其是在debugger拉胯或者缺少debug info的时候。


某个工厂的机器坏了,停工一天都损失惨重,老板好不容易请到一个维修师傅来修理。只见老师傅东看看西摸摸,十分钟之后给机器的某个地方换上了一个螺丝,机器就恢复了正常,修理费1000,老板很不服,说:「你的螺丝值一千?」老师傅说:「螺丝只值1块,知道换哪个螺丝值999」

具体的点很多人都说了,我就不再赘述了。其实这一类的疑惑都可以归结为,熟悉某种底层或者基础知识有什么实际意义。对于C++而言,这可能是虚表,可能是内存分布,可能是操作系统原理; 对于Java而言,可能是HashMap,可能是JVM; 对于Python或Lua,可能是部分复杂结构如dict或table的C++实现方式。甚至还包括演算法,数据结构等等更通用和基础的知识。

很多人会觉得,知道这些原理有什么用呢,我平时写逻辑又不用,我只管new一个新的HashMap,定义一个dict,table,class就行了,至于它做了什么事好像并无所谓。

是的,无所谓,前提是: 如果你没有遇上问题的话。

如果你不提前知道这些底层的实现原理,你是以什么方式来决定某个系统用什么结构存储比较合适比较高效呢?某个结构或底层介面有哪些边界情况和坑,存多大的数据之后性能衰减有多严重,是否线程安全,这些问题都依赖于你对这些底层的了解。以题目所述的虚表为例,如果不了解其实现原理,那就容易遇上一些你要比别人多花几倍时间解决的BUG。比如这个:

struct A {
int _info;
};

class B {
public:
B() {
_info = 0;
}
virtual~B() {
_info = 0;
}

int _info;
};

if ( sizeof(Base) == sizeof(int) ) {
// 做一些重要的事情
}
A a;
B b;
memcpy(b, a, sizeof(A));
if (a._info == b._info) {
// 另外一些重要的事情
}

我简化了情况,只保留了关键部分。我见过一个开发花了两天时间才搞明白为什么这两个if里都死活进不去。题主可以想一想,如果不知道虚表的原理,这个问题想找到根源应该很头疼。

很多基础知识就是这样,一般都没用。但程序员真正的价值就在于那999‰,否则你就只值螺丝的1块


实际意义就是不要瞎用虚函数,虚函数的出现是为了增加C++ OOP抽象性而设计出来的,虽然用起来很方便,但坑也很多,不能乱用,而且使用虚函数是有时间和空间overhead的,如果确定不会用到多态特性,那能不用虚函数就别用虚函数。


我这里有个错误示范,可以帮助你理解熟悉其底层实现有何作用。故事是这样子的,我使用 Boost 的状态机,写了如下的一段代码,目的是为了处理滑鼠点击移动事件,能够通过不同的操作到达不同状态:

// file : application_event_handler.hpp

#pragma once

#include &

#include &
#include &
#include &
#include &
#include &

class CustomApplicationEventHandlerBase;

class ApplicationEventHandler : public boost::msm::front::state_machine_def&
{
public:
class MoveEvent {};

class DownEvent {};

class UpEvent {};

class DownToUpFunctionType
{
public:
template &
void operator()(Event const event, FrontStateMachine front_state_machine, SourceState s, TargetState t)
{
}
};

class MoveToUpFunctionType
{
public:
template &
void operator()(Event const event, FrontStateMachine front_state_machine, SourceState s, TargetState t)
{
}
};

class DownToMoveFunctionType
{
public:
template &
void operator()(Event const event, FrontStateMachine front_state_machine, SourceState s, TargetState t)
{
}
};

class MoveToMoveFunctionType
{
public:
template &
void operator()(Event const event, FrontStateMachine front_state_machine, SourceState s, TargetState t)
{
}
};

class EmptyToDownFunctionType
{
public:
template &
void operator()(Event const event, FrontStateMachine front_state_machine, SourceState s, TargetState t)
{
auto machine = reinterpret_cast&< CustomApplicationEventHandlerBase&>(front_state_machine);

machine.empty_to_down_handler();
}
};

class Empty : public boost::msm::front::state&
{
public:
template &
void on_entry(Event const event, FrontStateMachine front_state_machine)
{
}

template &
void on_exit(Event const event, FrontStateMachine front_state_machine)
{
}
};

class Move : public boost::msm::front::state&
{
public:
template &
void on_entry(Event const event, FrontStateMachine front_state_machine)
{
}

template &
void on_exit(Event const event, FrontStateMachine front_state_machine)
{
}
};

class Down : public boost::msm::front::state&
{
public:
template &
void on_entry(Event const event, FrontStateMachine front_state_machine)
{
}

template &
void on_exit(Event const event, FrontStateMachine front_state_machine)
{
}
};

class Up : public boost::msm::front::state&
{
public:
template &
void on_entry(Event const event, FrontStateMachine front_state_machine)
{
}

template &
void on_exit(Event const event, FrontStateMachine front_state_machine)
{
}
};

template &
void on_entry(Event const event, FrontStateMachine machine) {}

template &
void on_exit(Event const event, FrontStateMachine machine) {}

using initial_state = Empty;
using handler = ApplicationEventHandler;
class transition_table : public boost::mpl::vector&< boost::msm::front::Row&,
boost::msm::front::Row&,
boost::msm::front::Row&,
boost::msm::front::Row&,
boost::msm::front::Row&,
boost::msm::front::Row&,
boost::msm::front::Row&,
boost::msm::front::Row&
&> {};
};

class CustomApplicationEventHandlerBase : public
boost::msm::back::state_machine&
{
public:
virtual void empty_to_down_handler() = 0;
};

注意,以上代码是可以编译的,有些代码已经删除,不过仍能体现我的问题所在。

上述代码中,ApplicationEventHandler 是状态机的前端类型,该类型定义了状态类状态转移处理函数,以及状态转移表,具体使用可以参考 Boost 的 State Machine 的文档,这个也是从示例代码抄过来的;

上述代码中,CustomApplicationEventHandlerBase 类型中定义了一个虚函数 empty_to_down_handler(), 该类型以及函数,我将在之后的 GUI 事件处理中进行实现。

下面,我给出一段使用该类型的代码,这段代码可以放在 main.cpp 中运行:

#include "application_event_handler.hpp"

class CustomApplicationEventHandler : public CustomApplicationEventHandlerBase
{
public:
virtual void empty_to_down_handler() override
{
std::cout &

运行时,我们就可以发现在

machine.empty_to_down_handler();

出现了运行错误,VS中提示:读取访问许可权冲突,machine 中并无 empty_to_down_handler() 的地址。

发现该错误后,我第一件事就是检查自己有没有写错,基本上是照著示例抄的,没问题(除了这个子类继承,但是把它改了我就没法实现我的设想了);

第二件事,我接著查看源代码,发现这个StateMachine一层套一层,虽然 CustomApplicationEventHandlerBase 继承的是 StateMachine 后端的模板,但实际上它也继承了前端,所以按道理在on_entry()等执行函数中,FrontStateMachine就应该是前端后端,事实上,在源代码中出错的地方调用的函数使用的也是this;

上述方法未能解决问题,于是我就在调用堆栈中进行追踪然后发现:

在出现错误的地方,machine 的地址总是和我们 main() 函数中 handler 的地址非常接近!!!而且,经过强转之后的 machine 的虚函数表中,所有函数都指向了一个空值。

事后,我才发现这里实际上跟我预想的出现了偏差,只有 machine 与 handler 的 地址相同 的时候,我再使用类型强转时,才能够获取虚函数当前的地址,才能进行正常的调用。

接上面继续,发现上述我并不熟悉的地方后,我在百度上查了查虚函数表以及C++类型数据结构,找了篇这样的文章:

C++多态虚函数表详解(多重继承、多继承情况)_青城山小和尚-CSDN博客?

blog.csdn.net图标

这篇文章的图,形象地指出了我所写的错误:

虚函数表的地址存在于子类对象的数据结构中,与此同时,该子类对象还保存著基类对象的起始地址,在我使用基类指针转成子类指针时,子类的虚函数表并不一定能够能够找到,此时调用子类虚函数基本上会出现未找到的问题。

实际上,还是本人学艺不精导致的。

修改方法嘛,我做个一个很直接的修改,就是将我需要的虚函数直接在 ApplicationEventHandler 中进行定义,同样可以达到我的目的,且不会出现问题。


让你知道所谓的子类型多态的本质是什么,使用的时候知道其开销有多少,怎么考虑开销,以及怎么避免一些坑。

再或者如果让你用C语言开发,你也可以用学到的虚函数知识,在C语言程序里用,比如手动构建函数指针表然后运行时查表跳转。

再往上推广到所有语言,运行时多态的本质是什么?就是保存一个额外的元数据,运行时根据这个元数据判断后续执行的代码是什么,管你什么c,c++,Java,py还是什么别的语言,都是一样的。而且你如果去深入研究,发现这些语言的多态实现原理甚至都是类似的。


空指针调用成员函数会崩溃吗 如果不会崩溃 什么情况不会

空指针调用虚函数会崩溃吗

这两个问题要搞清楚 和 虚函数指针表有关系


__declspec(dllexport) class Foo {

public:

Foo();

private:

std::vector&&> m_list;

};

不说虚函数,就说底层,了解编译器在幕后做了什么很有必要。例如解决上面这个代码片段的错误,第一次遇到能快速反应出哪里出了问题,而不是抓瞎。


虚函数是支撑许多设计模式实现的核心,而设计模式的自如运用是一个程序员走向成熟的重要标志,欢迎读我的新书《c++新经典:对象模型》,回答这种问题跟玩一样


无内鬼,来点 C++ 八股文。

要看多底层,面试问你的一般也不会太底层。我指的是具体的每个栏位。虽然我以前也有空分析过(用 VS 的),但不同编译器的实现还是有所不同。

大概主要用处是让你知道有这个东西,比如我前段时间改代码时编译出错:

undefined reference to `vtable for 【基类名】
undefined reference to `typeinfo for 【基类名】

至少知道问题出在虚基类上,比如有的方法没有实现。不过这是个比较奇葩的情况,一般没实现虚基类的方法会提示:

undefined reference to `【基类名】::【方法名】』

这只是一个例子。

其实更大的作用是让你面对 C++ 某些约定俗成的规定时,不至于像看待一个黑盒一样。虽说也算八股文,但至少比起熟悉模板元的各种玩法要有意义得多。

当然你研究得比一般的更深的话,面试官会觉得你是那种不放过技术细节的人,因此对你更有好感,这也算实际意义啊。

还有就是知道有了这个虚表之后,会多一层函数定位的开销。不过这事容易陷进去,该用虚函数的时候还是得用,真出现性能问题的时候,不到万不得已也没必要改。之前我受到这个观念的影响,无脑用 std::function 取代虚函数,然而发现并没有什么性能提升(似乎性能还下降了)。


当然是为了防止踩坑啦。各种语言都有其限制,了解限制能让你更好地带著镣铐跳舞。

谁不想在设计时放飞自我呢。如果能用python我干嘛非得用C++。


具体来说,为什么带虚函数后却不能memset,为什么非得加virtual特别是析构函数。这些东西并不优雅,但却是没有办法的事。


对逆向分析有一点点帮助


其实没啥实际意义,主要是debug的时候用…毕竟C++调试core dump实在不可避免。

生命苦短,用rust吧(


就我个人而言。。。理解实现之后,可以更直观的明白,父类指针指向子类的时候,子类指针指向父类的时候,调用的是哪个函数,而不用死记硬背,容易错。

任何事情都是吧,理解原理,修改应用都会更灵活一点。


基本没啥实际意义,debug还需要了解那么底层?最多就看个调用栈


一般来说意义并不大,但当你的应用逐渐复杂,环境牵扯逐渐增加时,熟悉包括但不限于虚函数的底层原理会让你不会在出错时手足无措。

比如你打算手撸COM介面,又或者不得不去实现一些多继承或者菱形继承之类的阴间玩意,那么了解这些知识就可以事半功倍。


调试以及多线程调试。

挂到莫名其妙的地方了,变数值不对了,this指针异常。请问不懂虚函数实现,不懂编译器怎么设置类对象内存结构的话如何解决?


虚表是c++实现多态的一种方式。你可以想想如果不用虚表,那怎么让编译器去实现多态?如果明白了这一点,那对c++语言的编译就会有更深的理解。


其实问这个的出发点是:

dynamic binding这么不自然的东西,你是否有那么一丢丢地好奇它是怎么实现的呢?


往大里说,保持好奇心是一个好的程序员的核心素质。

闻道有先后,技术掌握多少,磨过多少项目都只是量的问题——而对新事物的好奇与渴望,或者说对未知领域的求知欲,才是质的问题。


为啥用虚函数,你首先得明白。不是为了OOP而OOP。没有虚函数,许多设计模式就完全没有存在基础,模块的可重用性也就无从谈起。虚函数的存在是很多框架如MFC、VCF、QT的基础。在虚函数之上,更近一步的抽象和可重用是OLE/COM技术。了解虚函数的实现,会有助于你明白内存布局,消息转发处理流程等等,也会对于什么是指针类型,如何实现指针的动态类型转换有一个比较明确的概念,更是理解指向函数成员指针、绑定等概念的基础。

关于虚函数的实现,除了候捷老师翻译的inside c++ object model(强烈推荐,中英按页对照,添加了很多候老师的理解和一些修正),建议还可以深入读一读 Imperfect C++。读完之后,你就能理解虚函数究竟怎么回事,甚至于用纯C也能实现虚函数的功能,包括怎样实现编译器无关的虚函数内存布局等等,里面有很多例子会让你明白关于虚函数的现实开发问题。


推荐阅读:
相关文章