2

I have been studying c++ for an exam and I thought that i had understood most of the c++ commons misconcemptions with much fatigue but i've encountered an exercise from a past exam that is driving me crazy, it combines virtual methods and inheritance in a way that i dont seem to understand here is the code:

    #include <iostream>

    class B;

class A {
    public:
    virtual A* set(A* a) = 0;
};

class B : public A {
    public:
    virtual A* set(B* b) {
            std::cout << "set1 has been called" << std::endl;
            b = this;
            return b;
    }

    virtual B* set(A* a) {
            std::cout << "set2 has been called" << std::endl;
            a = this;
            return this;
    }
};

int main(int argc, char *argv[]) {
    B *b = new B();
    A *a = b->set(b);
    a = b->set(a);
    a = a->set(b);
    a = a->set(a);
    return 0;
}

the output is

set1 has been called
set2 has been called
set2 has been called
set2 has been called

From what i've gathered the first call (b->set(b) ) calls the first method of class B and return b itself and then this objectref gets casted to A meaning that now the object b is now of type A? so i have A *a = A *b; now it makes sense to me that i should call set of A since i have this situation in my mind objectoftypeA->set(objectoftypeA) so i m not supposed to look into virtual methods since the two object are base classes ?

Anyway as you can see I have much confusion so bear with me if i make stupid errors i would be glad if someone could explain whats going on this code,i tried to search the web but i find only small and easy example that dont cause troubles.

9
  • 1
    It would be helpful if you described what you expect. You've just thrown a lot of code at us, and described in a run-on sentence what you're not sure of, which is a bit hard to follow. Hard to know where to start answering. Commented Jul 8, 2012 at 14:34
  • I would expect that the second call calls again set1 but it doesnt so i guess that my whole line of thinking is wrong Commented Jul 8, 2012 at 14:36
  • 1
    Im not quite sure on this but the example is really confusing though. IMO i think virtual B* set(A* a) alias set2 will act as implementation of virtual A* set(A* a) = 0; as a B* can implicitely be casted to an A*. Thus once you received a A* from set1 the implmentatino will be called which is set2. EDIT: Notice that set1 cant implement the pure virtual function due to its specialied parameter. Try to comment out each function. I gueess ` B *b = new B();` will cause an error if set2 is not there but not if set1 is not there. Commented Jul 8, 2012 at 14:36
  • Please don't write code like this yourself :) Commented Jul 8, 2012 at 14:46
  • @jrok This could easily happen in real life, although one should always be careful when mixing overloading with overriding. C++11 provides pseudo-keywords final and override to prevent accidentally writing this sort of thing. Commented Jul 8, 2012 at 14:47

2 Answers 2

4

The program demonstrates how member functions are looked up. The static type of the object determines the function overload that will be called: it performs the name lookup. The dynamic type then determines the virtual override that gets called.

Perhaps the key point is that different overlods of the same name are really different functions.

Since A has only one set member, there is only one thing that can happen when you call a->set(), no matter what the argument is. But when you call b->set(), there are a couple potential functions, and the best one is selected.

Since B::set is never overridden, it makes no difference whether it's virtual or not. virtual members of the same class don't talk to each other at all.

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

1 Comment

@Cheersandhth.-Alf Yeah, it's like that… 90% of the work is outside the programming aspects.
2

Potatoswatter is right, but I think I have a bit "clearer" explanation. I think the OP is getting confused on what happens at run-time with dynamic type lookup versus compile-time, and when up-casting happens automatically, versus when it does not.

First off, return type does NOT affect which overload is called. You probably know that, but it bears repeating. A return type mis-match will cause an error at compile-time, but not run-time, and does not affect which overload is called. Also it's worth noting that as long as it is a compatible pointer type (in a hierarchy together) returning a pointer doesn't ever "change" it. It is still the same pointer, unlike converting floats to ints, where there is an actual change.

Now to go through the calls one-by-one. This is my understanding of the process, not necessarily what the standard, or what "really" happens.

When you call b->set(b) the compiler (not run-time) goes "looking for a method named set with an argument of pointer to B" which it finds with the one that outputs set1. It's virtual, so there's code to check if the class points to anything lower, but there isn't, so it just calls it, and returns the this pointer into a.

Now you're calling b->set(a). Again it's the compiler that goes "does b have an overload that takes pointer to A?" Yes it does, so it calls the "set2" method. It's the compiler that sees an A* and so the call is "determined" at that point. Even though the pointer points to an object that is of type B, the compiler doesn't know that, or care. So it's the compile-time types of the arguments that determine which overloaded method get taken. From that point on, where in the hierarchy the virtual gets taken is on the underlying type of the this pointer, but only downward.

Here's a different case though. Try this: b->set(dynamic_cast<B*>(a)) This should call the "set1" method, because the compiler is going to definitely have a pointer to B (even if it's nullptr).

Now the third case: a->set(b). What's happening here is the compiler says "there is only one set method, so can the argument be up-cast or constructed to that type?" The answer is yes, as B is a child of A. So that cast happens transparantly, and the compiler calls the ABSTRACT dispatcher for the set method of the type A. This occurs at compile time before the "real" type of what a is pointer to. Then at run-time, the program "walks the virtual" and finds the lowest one, the B->set(A*) method that emits "set2". The actual type of what the argument points to isn't used, only the type to the left of the arrow operator, and that only determines how far down the hierarchy.

And the fourth case is just the 3rd again. The type of the argument (the pointer, not whta is pointed to) is compatible, so it goes as before. If you want a dramatic demonstration of this, try this:

a->set((A*)nullptr) // prints "set2 has been called"
b->set((A*)nullptr) // prints "set2 has been called"
b->set((B*)nullptr) // prints "set1 has been called"

The underlying type of what the arguments point to doesn't affect dynamic dispatch. Only their "surface" type affects the overload called.

1 Comment

I finally get it your explanation was simplier and more targeted to my unknowledge of c++ i wish my teacher could explain like this,some people forget that everyone start with no knowledge and need a walkthrough for some things

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.