在c ++中派生对象和基础对象之间有什么区别? [英] What's the difference between a derived object and a base object in c++?

查看:248
本文介绍了在c ++中派生对象和基础对象之间有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

特别是当类中有一个虚拟函数时,c ++中的派生对象和基础对象之间的区别是什么?




$ b

派生对象是否保存其他表来保存指针



到函数?

解决方案

派生对象继承了基类的所有数据和成员函数。根据继承(公共,私有或受保护)的性质,这将影响这些数据和成员函数对您类的客户端(用户)的可见性。



说,你从A私下继承B,像这样:

  A类
{
public:
void MyPublicFunction();
};

class B:private A
{
public:
void MyOtherPublicFunction();
};

即使A具有公共函数,B的用户也不会看到。示例:

  B * pB = new B 
pB-> MyPublicFunction(); //这将不会编译
pB-> MyOtherPublicFunction(); // This is OK

由于私有继承,所有数据和成员函数A虽然对B类中的B类可用,但不可用于仅使用B类的实例的代码。



如果使用公共继承,即:

  B类:public A 
{
...
};

,那么A的所有数据和成员都将被B类的用户看到。这种访问仍然受到A的原始访问修饰符的限制,即A中的私有函数永远不会被B的用户访问(或者,为B类本身的代码)。此外,B可以重新命名与A中的名称相同的函数,从而将这些函数隐藏给B类的用户。



对于虚函数, 。



例如:

  class A 
{
public:
int MyFn(){return 42; }
};

class B:public A
{
public:
virtual int MyFn(){return 13; }
};



如果您尝试调用 MyFn()通过A *类型的指针在B对象上,那么虚拟函数不会被调用。



例如:

  A * pB = new B(); 
pB-> MyFn(); //将返回42,因为A :: MyFn()被调用。

但我们假设我们将A更改为:

  class A 
{
public:
virtual void MyFn(){return 42; }
};

(注意A现在声明 MyFn() 虚拟



,则结果如下:

  A * pB = new B(); 
pB-> MyFn(); //将返回13,因为B :: MyFn()被调用。

这里, MyFn()的B版本被调用是因为类A已经将 MyFn()声明为虚拟的,所以编译器知道它必须在调用 MyFn()。或者一个对象,它认为它是一个A,在这种情况下,即使我们创建了一个B对象。



所以对于你的最后一个问题,函数存储?



这是编译器/系统相关的,但是最常用的方法是使用一个类的实例,该类有一个虚函数继承自一个基类),这样的对象中的第一块数据是一个特殊指针。这个特殊指针指向一个'虚函数指针表',或者通常缩写为' vtable '。



编译器为其编译的每个具有虚函数的类创建vtables。所以对于我们的最后一个例子,编译器将生成两个vtables - 一个用于类A,一个用于类B.这些表有单个实例 - 对象的构造函数将在每个新创建的对象中设置vtable指针到正确的vtable块。



请记住,带有虚函数的对象中的第一个数据是指向vtable的指针,因此编译器总是知道如何找到vtable,需要调用一个虚函数。所有编译器都要做的是查看任何给定对象中的第一个内存槽,并且它有一个指向该对象类的正确的vtable的指针。



我们的例子是非常简单 - 每个vtable是一个条目长,所以它们看起来像这样:



vtable for A类:

  + --------- + -------------- + 
| 0:MyFn | - > A :: MyFn()|
+ --------- + + -------------- +

vtable for B class:

  + --------- + ------------- + 
| 0:MyFn | - > B :: MyFn()|
+ --------- + -------------- +

注意,对于 B 类的vtable, MyFn 的条目被指向 B :: MyFn()的指针覆盖 - 这确保当我们调用虚函数 MyFn() B 版本 MyFn(),即使对象类型 A * 正确呼叫,而不是 A :: MyFn()



'0'数字表示表中的入口位置。在这个简单的情况下,每个vtable中只有一个条目,因此每个条目都在索引0处。



因此,调用 MyFn A B )对象(代码如下:

  pB-> __ vtable [0] 

(这不会编译;它只是编译器将生成的代码的解释。 )



为了使它更明显,让我们说 A 声明另一个函数, MyAFn ),这是虚拟的,B不会覆盖/重新实现。



因此代码将是:

  class A 
{
public:
virtual void MyAFn(){return 17; }
virtual void MyFn(){return 42; }
};

class B:public A
{
public:
virtual void MyFn(){return 13; }
};

那么B将具有 MyAFn() MyFn(),其vtables现在将如下所示:



  + ---------- + ------------- -  + 
| 0:MyAFn | - > A :: MyAFn()|
+ ---------- + --------------- +
| 1:MyFn | - > A :: MyFn()|
+ ---------- + --------------- +

vtable for B class:

  + --------- -  + --------------- + 
| 0:MyAFn | - > A :: MyAFn()|
+ ---------- + --------------- +
| 1:MyFn | - > B :: MyFn()|
+ ---------- + --------------- +

因此,在这种情况下,要调用 MyFn(),编译器将生成如下代码:

  pB-> __ vtable [1](); 

因为 MyFn()



显然,调用 MyAFn()会产生如下代码:

  pB-> __ vtable [0](); 

因为 MyAFn()位于索引0 。



应该强调的是,这是编译器依赖的,而iirc,编译器没有义务按照它们声明的顺序在vtable中排序函数 -



在实践中,这个方案被广泛使用,vtables中的函数排序是相当确定的,所以ABI在不同C ++编译器生成的代码之间维护,并允许COM互操作和类似的机制跨不同编译器生成的代码的边界工作。 这绝对不能保证。



幸运的是,你永远不必担心vtables,但它绝对有助于你的心理模型的意义是有意义的,并且不会为你在未来存储任何惊喜。


What's the difference between a derived object and a base object in c++,

especially, when there is a virtual function in the class.

Does the derived object maintain additional tables to hold the pointers

to functions?

解决方案

The derived object inherits all the data and member functions of the base class. Depending on the nature of the inheritance (public, private or protected), this will affect the visibility of these data and member functions to clients (users) of your class.

Say, you inherited B from A privately, like this:

  class A
  {
    public:
      void MyPublicFunction();
  };

  class B : private A
  {
    public:
      void MyOtherPublicFunction();
  };

Even though A has a public function, it won't be visible to users of B, so for example:

  B* pB = new B();
  pB->MyPublicFunction();       // This will not compile
  pB->MyOtherPublicFunction();  // This is OK

Because of the private inheritance, all data and member functions of A, although available to the B class within the B class, will not be available to code that simply uses an instance of a B class.

If you used public inheritance, i.e.:

  class B : public A
  {
    ...
  };

then all of A's data and members will be visible to users of the B class. This access is still restricted by A's original access modifiers, i.e. a private function in A will never be accessible to users of B (or, come to that, code for the B class itself). Also, B may redeclare functions of the same name as those in A, thus 'hiding' these functions from users of the B class.

As for virtual functions, that depends on whether A has virtual functions or not.

For example:

  class A
  {
    public:
      int MyFn() { return 42; }
  };

  class B : public A
  {
    public:
      virtual int MyFn() { return 13; }
  };

If you try to call MyFn() on a B object through a pointer of type A*, then the virtual function will not be called.

For example:

A* pB = new B();
pB->MyFn(); // Will return 42, because A::MyFn() is called.

but let's say we change A to this:

  class A
  {
    public:
      virtual void MyFn() { return 42; }
  };

(Notice A now declares MyFn() as virtual)

then this results:

A* pB = new B();
pB->MyFn(); // Will return 13, because B::MyFn() is called.

Here, the B version of MyFn() is called because the class A has declared MyFn() as virtual, so the compiler knows that it must look up the function pointer in the object when calling MyFn() on an A object. Or an object it thinks it is an A, as in this case, even though we've created a B object.

So to your final question, where are the virtual functions stored?

This is compiler/system dependent, but the most common method used is that for an instance of a class that has any virtual functions (whether declared directly, or inherited from a base class), the first piece of data in such an object is a 'special' pointer. This special pointer points to a 'virtual function pointer table', or commonly shortened to 'vtable'.

The compiler creates vtables for every class it compiles that has virtual functions. So for our last example, the compiler will generate two vtables - one for class A and one for class B. There are single instances of these tables - the constructor for an object will set up the vtable-pointer in each newly created object to point to the correct vtable block.

Remember that the first piece of data in an object with virtual functions is this pointer to the vtable, so the compiler always knows how to find the vtable, given an object that needs to call a virtual function. All the compiler has to do is look at the first memory slot in any given object, and it has a pointer to the correct vtable for that object's class.

Our case is very simple - each vtable is one entry long, so they look like this:

vtable for A class:

+---------+--------------+
| 0: MyFn | -> A::MyFn() |
+---------+--------------+

vtable for B class:

+---------+--------------+
| 0: MyFn | -> B::MyFn() |
+---------+--------------+

Notice that for the vtable for the B class, the entry for MyFn has been overwritten with a pointer to B::MyFn() - this ensures that when we call the virtual function MyFn() even on an object pointer of type A*, the B version of MyFn() is correctly called, instead of the A::MyFn().

The '0' number is indicating the entry position in the table. In this simple case, we only have one entry in each vtable, so each entry is at index 0.

So, to call MyFn() on an object (either of type A or B), the compiler will generate some code like this:

pB->__vtable[0]();

(NB. this won't compile; it's just an explanation of the code the compiler will generate.)

To make it more obvious, let's say A declares another function, MyAFn(), which is virtual, which B does not over-ride/re-implement.

So the code would be:

  class A
  {
    public:
      virtual void MyAFn() { return 17; }
      virtual void MyFn()  { return 42; }
  };

  class B : public A
  {
    public:
      virtual void MyFn() { return 13; }
  };

then B will have the functions MyAFn() and MyFn() in its interface, and the vtables will now look like this:

vtable for A class:

+----------+---------------+
| 0: MyAFn | -> A::MyAFn() |
+----------+---------------+
| 1: MyFn  | -> A::MyFn()  |
+----------+---------------+

vtable for B class:

+----------+---------------+
| 0: MyAFn | -> A::MyAFn() |
+----------+---------------+
| 1: MyFn  | -> B::MyFn()  |
+----------+---------------+

So in this case, to call MyFn(), the compiler will generate code like this:

pB->__vtable[1]();

Because MyFn() is second in the table (and so at index 1).

Obviously, calling MyAFn() will cause code like this:

pB->__vtable[0]();

because MyAFn() is at index 0.

It should be emphasised that this is compiler-dependent, and iirc, the compiler is under no obligation to order the functions in the vtable in the order they are declared - it's just up to the compiler to make it all work under the hood.

In practice, this scheme is widely used, and function ordering in vtables is fairly deterministic, so ABI between code generated by different C++ compilers is maintained, and allows COM interoperation and similar mechanisms to work across boundaries of code generated by different compilers. This is in no way guaranteed.

Luckily, you'll never have to worry much about vtables, but it's definitely useful to get your mental model of what is going on to make sense and not store up any surprises for you in the future.

这篇关于在c ++中派生对象和基础对象之间有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆