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