3

I define 2 classes

class BaseA { public: virtual void methodA() = 0; }; class BaseB { public: virtual void methodB(int val) = 0; }; 

Child inherits 2 Base Class

class Child : public BaseA, public BaseB { public: void methodA() override { printf("Child A\n"); } void methodB(int val) override { printf("Child B %d\n", val); } }; 

Then I write following code.

void callBaseB(void *p) { BaseB *b = (BaseB *) p; b->methodB(0); } int main() { auto child = new Child; callBaseB(child); return 0; } 

console print Child A
Why this happened? Why not call method B?

(This is what happend when a Java engineer try to write C++ code)

20
  • 5
    You can't cast a void * to a BaseB * like that. You have completely flummoxed the compiler. Commented Feb 27, 2022 at 12:19
  • 1
    What is the purpose of void* here instead of using BaseB*? void* is one of those C++ constructs that should only be used if there is no other way to solve the problem and only if being very careful not to make a mistake. Commented Feb 27, 2022 at 12:22
  • 1
    You must cast void* back to original type and nothing else. Related/duplicate: C++ typecast: cast a pointer from void pointer to class pointer, Casting to void* and Back to Original_Data_Type* Commented Feb 27, 2022 at 12:23
  • 1
    @desperateLord The caller has to first cast to the base class, before passing the pointer to the function. Commented Feb 27, 2022 at 12:25
  • 1
    @SergeyKolesnik "one is a standard-layout class object" is supposed to apply to both parts of the "or". And all classes involved are not standard-layout because they have virtual functions. Commented Feb 27, 2022 at 12:29

2 Answers 2

5

You should just do this: void callBaseB(BaseB *p) {p->methodB(0);}.

If you want to keep void *p as a parameter, you need to cast it to exactly Child * first. Either:

BaseB *b = (Child *)p; b->methodB(0); 

Or:

Child *b = (Child *)p; b->methodB(0); 

Alternatively, cast to BaseB * before converting to void *. Then casting from void * back to Base * will work.


What happens here is that the BaseB subobject is at non-zero offset inside of Child.

When you convert Child * to BaseB * (either explicitly with a cast, or implicitly, either by assigning pointers or by calling a method of BaseB on the pointer), the compiler automatically offsets the pointer by the required amount, to point to the BaseB subobject.

The offset is determined entirely at compile-time (unless virtual inheritance is involved), based on the two types.

When you obscure the source type using void *, the compiler has no way to determine the correct offset, and doesn't even try to apply any offset, so you get weird behavior, because the resulting BaseB * doesn't point to a BaseB instance, but rather to some junk inside of Child.

Even with virtual inheritance, despite the offset being determined at runtime, the calculation depends on the source pointer type being known.

Sign up to request clarification or add additional context in comments.

4 Comments

I try using your method and print the pointer. Yes, the pointer skewed about 8 when I pass to BaseB* type parameter. It makes a lot sense now. Thx.
Hmmm. Please see my comment to @solvedgames answer starting '...'
@PaulSanders "Now you are assuming that the caller always passes a Child *" Yes, and there's no workaround for this, except for getting rid of void * parameter. It's up to the caller to adjust the pointer, and the caller needs to know the target type to do it.
@HolyBlackCat Sure, but that target type might as well be a Base *, since that is what callBaseB actually wants. Forcing it to assume that the caller is, in actuality, passing a Child * is unnecessarily inflexible.
0

Casting a void* to a BaseB* cannot be done (as @PaulSanders said), but you can definitely cast a Child* to a BaseB* as follows:

void callBaseB(Child* p) { BaseB* b = p; b->methodB(0); } 

The above code should successfully call methodB().

But if you really need to use void*, you can so something like this:

void callBaseB(void* p) { Child* b = (Child*)p; b->methodB(0); } 

9 Comments

Yes, It can. But my senario I save pointer to Java Long. So I just have to cast void pointer.
I've edited the answer to use void*.
You don't need a cast at all here. You can just do BaseB* b = p;. And it doesn't solve the OP's stated goal of passing a void * into callBaseB for whatever reason he has to do that.
I have edited the answer to fulfill that requirement of the OP.
... and if anything, your second proposal makes things even worse. Now you are assuming that the caller always passes a Child * to callBaseB, and I don't think the OP has given you any reason to do that.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.