Can this change in subclass require recompilation of code dependent on superclass?

StackOverflow https://stackoverflow.com/questions/17224823

  •  01-06-2022
  •  | 
  •  

سؤال

I have been learning some more "indepth" things about virtual tables recently and this question came to my mind.

Suppose we have this sample:

class A {
 virtual void foo();
}

class B : public A {
 void foo();
}

In this case from what I know there will be a vtable present for each class and the dispatch would be quite simple.

Now suppose we change the B class to something like this:

class B : public C, public A {
  void foo();
}

If the class C has some virtual methods the dispatch mechanism for B will be more complicated. There will probably be 2 vtables for both inheritance paths B-C, B-A etc.

From what I've learned so far it seems that if there would be somewhere else in the codebase function like this:

void bar(A * a) {
  a->foo();
}

It would need to compile now with the more complicated dispatch mechanism because at compile time we do not know if "a" is pointer to A or B.

Now to the question. Suppose we added the new class B to our codebase. It doesn't seem likely to me that it would require to recompile the code everywhere where the pointer to A is used.

From what I know the vtables are created by the compiler. However is it possible that this fixup is solved by the linker possibly during relocation? It does seem likely to me I just can not find any evidence to be sure and therefore go to sleep right now :)

هل كانت مفيدة؟

المحلول

Inside void bar(A * a), the pointer is definitely to an A object. That A object may be a subobject of something else like a B, but that's irrelevant. The A is self-contained and has its own vtable pointer which links to foo.

When the conversion from B * to A * occurs, such as when bar is called with a B *, a constant offset may be added to the B * to make it point to the A subobject. If the first thing in every object is the vtable, then this will also set the pointer to the A vtable as well. For single inheritance, the adjustment is unnecessary.

Here is what memory looks like for a typical implementation:

| ptr to B vt | members of C | members of B | ptr to AB vt | members of A |

B vt: | ptrs to methods of C (with B overrides) | ptrs to methods of B |
AB vt: | ptrs to methods of A (with B overrides) |

(Note that typically the AB vt is really still part of the B vt; they would be contiguous in memory. And ptrs to methods of B could then go after ptrs to methods of A. I just wrote it this way for formatting clarity.)

When you convert a B * to an A *, you go from this:

| ptr to B vt | members of C | members of B | ptr to AB vt | members of A |
^ your B * pointer value

to this:

| ptr to B vt | members of C | members of B | ptr to AB vt | members of A |
                                            ^ your A * pointer value

Using a static_cast from A * to B * will move the pointer backwards, in the other direction.

نصائح أخرى

No, there's no need to recompile the code that depends only on A.

This is actually immediately follows from the principle of "independent translation", which typical modern C++ compilers adhere to. You can write all code dependent only on A in such a way, that it will never include any definitions that mention B. That means that any changes in B will not trigger the recompilation of any A-specific code. That in turn means that a C++ compiler that follows the principle of "independent translation" has to implement simple inheritance, multiple inheritance, virtual dispatch, hierarchical conversions etc. in such a way that any changes in B-specific code do not require recompilation of any A-specific code.

If that wasn't the case, the architecture of a typical C++ compiler would have to be significantly different.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top