如何在C ++内部实现类?

时间:2015-11-24 10:41:33

标签: c++ class

我尝试使用谷歌搜索,但没有找到任何结果。使用函数指针和数据的结构,实现类似于C中的类hack吗?或者它是以另一种形式实现的?

4 个答案:

答案 0 :(得分:2)

我没有编写任何c ++编译器,但类的结构应该包含:

  • 基类数据部分(包含类的基类的所有数据)
  • 类实例数据部分(包含类实例的所有数据)
  • 虚拟表指针(如果类或它的基础具有至少一个虚函数)

注意:非虚拟函数是静态引用的,因此它们不应直接绑定到类实例。

也就是说,每个编译器可能会以不同的方式实现它。

TLDR :基本上是的,实现必须类似于类hack(但这应该对开发人员来说是透明/无关的。)

修改

这篇文章主要是基于多年来使用visual studio进行调试(非常主观)的推测,多年前支持一些经验,维护一个具有类hack支持继承的项目,用C实现(这种体验非常主观,如同孔)。

例如,在Visual Studio 6中,您可以看到虚拟函数表是在c ++实现特定数据之前分配的。也就是说,一个班级看起来像这样:

[vtbl][data]
^1    ^2

所以,如果这是(例如)struct X { virtual ~X(); int i; },那么写:

X a;
X *p = &a;

会创建类似于此的内容:

[ptr + 1] -> any other virtual functions
[ptr + 0] -> X::~X
^x

[^x][data]
^1  ^2
    ^p = ^2;

^1是操作系统分配内存的位置(并且vtbl将在new的实现中填充),那么用户数据的偏移量(vtbl + sizeof (vtbl))将是返回到客户端代码,作为类的地址。我不知道是否仍然如此。

答案 1 :(得分:2)

是的,通常(或者至少有时候)它将以一种可以使其与“C-hack”兼容的方式实现,但细节因编译器而异。

由于以下几个原因,这变得非常自然:

  • C被设计为允许低级编程,这表明处理器上可用的所有数据类型也可用于C实现。
  • C / C ++的实现经常捆绑在同一个产品中,这将产生大量的共享代码,因此C ++中使用的基本数据类型可以很容易地用C语言提供。
  • 类布局所需的数据类型基本上是已经存在的C / C ++语言类型。

基本上,以一种可以在随附的C实现中描述类布局的方式来实现C ++是“容易的”,但也不“不”这样做。“/ p>

作为GCC布局的一个简单例子,我们可以考虑使用虚方法的类(如果它没有虚方法,那么它将是一个简单的struct layoutwise)。然后,布局将以虚拟表指针开始,该指针指向一个单词/指针数组(包含函数指针,指向type_info节点的指针,以及一些其他有用信息)。然后跟随成员,好像它是一个普通的结构。

当继承它类似时,它从基类的布局开始(自然因为指向派生类的指针应该很容易转换为基类的指针),除非现在虚拟表指针指向虚拟表派生类的顺序(顺便说一下,它具有与基类相同的布局,除了它可能有与派生类中引入的新虚拟方法相对应的附加元素 - 这里指向派生类的虚拟表的指针可以作为基类的虚拟表)。

在C中,这看起来像是:

struct class_layout {
     void **__vptr;

     /* 
     base data members as they appear in the C++ definitions,
     (given they have fundamental types)
      */

     /*
      additional data members introduced in the derived class
     */
};

__vptr定义为包含函数指针的struct的指针是很诱人的,它会正常工作,除非它与GCC的工作方式不兼容。细节是虚拟表中的条目也是负指数(可能由于历史原因)。

然后有些情况需要特别小心:例如,没有虚方法的基类(几乎)要求基类中没有虚拟表指针。虚拟继承的情况需要指向基类对象的指针。

答案 2 :(得分:1)

通常,答案是"是",因为这是最直接的方式。但是,绝不是保证。标准没有说明编译器必须做什么,它只是说明在给定条件下必须发生的事情。

在最简单,最简单的情况下,class与成员默认为struct的{​​{1}}相同。由于你无法对private做任何事情(一切都是私密的,没有公共成员,没有构造函数),所以它毫无意义。

在下一个最简单的情况下,struct也将具有成员函数,包括构造函数和析构函数。
成员函数隐含地具有不同的调用约定,因为传递了隐式class指针,但除此之外它们只是普通的普通函数。通常情况下,编译器会将名称变成类似this的内容,其中每个" magic"字母和数字具有明确的,依赖于编译器的含义(我试图在GCC中一起修改一个正确的错误名称,手动执行这一操作有点单调乏味)。 在创建和销毁对象时,编译器还将代表自己调用构造函数和析构函数。除此之外,这些基本上都是普通的普通函数"(我们将在这里忽略很少的额外怪癖)。

然后,可能有虚函数。为了实现这一点,通常,在结构的数据部分前面添加一个指向全局函数指针表的指针(每个对象一个指针,但对于该类型的所有对象,全局只有一个表)。
再次,就像破坏一样,这是一个实现细节,而不是标准要求的东西(实际上,它是如何在几乎所有地方完成的)。

在最复杂的情​​况下,添加了几个虚函数指针,并且编译器根据当前对象的类型秘密调整对象的指针(投射指针可能确实给出了不同的地址) ,也)。这可以确保"正确"在不必知道的情况下调用虚函数。

答案 3 :(得分:1)

您可能会发现此演示文稿既有趣又有用: http://www.hexblog.com/wp-content/uploads/2011/08/Recon-2011-Skochinsky.pdf

通常,非虚拟类的布局或多或少与C中的布局类似。

虚拟类必须存储虚函数表,指向运行时类型信息的指针和'偏移',以便可以在任意复杂的派生类中找到数据。

这三个对象被折叠到虚拟功能表中。