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

前端 未结 10 1819
一个人的身影
一个人的身影 2021-01-01 06:23

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

相关标签:
10条回答
  • 2021-01-01 06:49

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

    A derived object can be used in place of a base object; it has all the members of the base object, and maybe some more of its own. So, given a function taking a reference (or pointer) to the base class:

    void Function(Base &);
    

    You can pass a reference to an instance of the derived class:

    class Derived : public Base {};
    Derived derived;
    Function(derived);
    

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

    If the derived class overrides a virtual function, then the overridden function will always be called on objects of that class, even through a reference to the base class.

    class Base
    {
    public:
        virtual void Virtual() {cout << "Base::Virtual" << endl;}
        void NonVirtual()      {cout << "Base::NonVirtual" << endl;}
    };
    
    class Derived : public Base
    {
    public:
        virtual void Virtual() {cout << "Derived::Virtual" << endl;}
        void NonVirtual()      {cout << "Derived::NonVirtual" << endl;}
    };
    
    Derived derived;
    Base &base = derived;
    
    base.Virtual();      // prints "Derived::Virtual"
    base.NonVirtual();   // prints "Base::NonVirtual"
    
    derived.Virtual();   // prints "Derived::Virtual"
    derived.NonVirtual();// prints "Derived::NonVirtual"
    

    Does the derived object maintain additional tables to hold the pointers to functions?

    Yes - both classes will contain a pointer to a table of virtual functions (known as a "vtable"), so that the correct function can be found at runtime. You can't access this directly, but it does affect the size and layout of the data in memory.

    0 讨论(0)
  • 2021-01-01 06:52

    Are you asking about the respective objects' representation in memory?

    Both the base class and the derived class will have a table of pointers to their virtual functions. Depending on which functions have been overridden, the value of entries in that table will change.

    If B adds more virtual functions that aren't in the base class, B's table of virtual methods will be larger (or there may be a separate table, depending on compiler implementation).

    0 讨论(0)
  • 2021-01-01 07:00

    The derived object is derived from its base object(s).

    0 讨论(0)
  • 2021-01-01 07:01

    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.

    0 讨论(0)
  • 2021-01-01 07:05

    More theoretically, if you derive one class from another, you have a base class and a derived class. If you create an object of a derived class, you have a derived object. In C++, you can inherit from the same class multiple times. Consider:

    struct A { };
    struct B : A { };
    struct C : A { };
    struct D : B, C { };
    
    D d;
    

    In the d object, you have two A objects within each D objects, which are called "base-class sub-objects". If you try to convert D to A, then the compiler will tell you the conversion is ambiguous, because it doesn't know to which A object you want to convert:

    A &a = d; // error: A object in B or A object in C?
    

    Same goes if you name a non-static member of A: The compiler will tell you about an ambiguity. You can circumvent it in this case by converting to B or C first:

    A &a = static_cast<B&>(d); // A object in B
    

    The object d is called the "most derived object", because it's not a sub-object of another object of class type. To avoid the ambiguity above, you can inherit virtually

    struct A { };
    struct B : virtual A { };
    struct C : virtual A { };
    struct D : B, C { };
    

    Now, there is only one subobject of type A, even though you have two subobject that this one object is contained in: subobject B and sub-object C. Converting a D object to A is now non-ambiguous, because conversion over both the B and the C path will yield the same A sub-object.

    Here comes a complication of the above: Theoretically, even without looking at any implementation technique, either or both of the B and C sub-objects are now not contiguous anymore. Both contain the same A object, but both doesn't contain each other either. This means that one or both of those must be "split up" and merely reference the A object of the other, so that both B and C objects can have different addresses. In linear memory, this may look like (let's assume all objecs have size of 1 byte)

    C: [1 byte [A: refer to 0xABC [B: 1byte [A: one byte at 0xABC]]]]
       [CCCCCCC[                  [BBBBBBBBBBCBCBCBCBCBCBCBCBCBCB]]]]
    

    CB is what both the C and the B sub-object contains. Now, as you see, the C sub-object would be split up, and there is no way without, because B is not contained in C, and neither the other way around. The compiler, to access some member using code in a function of C, can't just use an offset, because the code in a function of C doesn't know whether it's contained as a sub-object, or - when it's not abstract - whether it's a most derived object and thus has the A object directly next to it.

    0 讨论(0)
  • 2021-01-01 07:06

    base- is the object you are deriving from. derived - is the object the inherits his father's public (and protected) members.

    a derived object can override (or in some cases must override) some of his father's methods, thus creating a different behavior

    0 讨论(0)
提交回复
热议问题