user2830071
user2830071

Reputation: 33

How does C++ identify which function to use (class member function pointers related)

I have a question regarding class member functions. This one involves inheritance. I wrote the following code below but I do not really understand exactly how it works (I can only guess):

#include <iostream>

using namespace std;

class Base
{
};

typedef void(Base::*handler)();

#define selector(_SELECTOR) static_cast<handler>(&_SELECTOR)

class Boo : public Base
{
public:
    void callingFunc()
    {
        cout << "Hey there" << endl;
    }

};


class Foo
{   
public:
    void setCallback( Base * instance,  void (Base::*funcToCall)(void) )
    {
        this->instance = instance;
        this->funcToCall = funcToCall;
    }

    void doCall()
    {
        (instance->*funcToCall)();
    }

private:
    Base* instance;
    void (Base::*funcToCall)(void);
};

void main()
{
    Foo * foo = new Foo();
    Boo * boo = new Boo();

    foo->setCallback(boo, selector(Boo::callingFunc) );
    foo->doCall();  // outputs "Hey there"
}

This code works, but I want to know the details why. doCall() seems to upcast funcToCall to the type (void (Boo::*)(void)) from (void (Base::*)(void)). It also seems to upcast my instance variable into Boo! It magically seems to know that the funcToCall I gave belongs to Boo and casts accordingly.

How is this being done? Is this done on runtime or compile time? Does it simply attempt to look for the name of the function when I invoke (instance->*funcToCall)();?

And don't nitpick me about typedef. I know some stuff needs to be typedef for readability. This is just test code.

EDIT: I played around more with the code and it just seems stranger. I added a new class and this is the outcome:

class Goo : public Base
{
public:
    void callingFunc()
    {
        cout << "Yo there" << endl;
    }
};

void main()
{
    Foo * foo = new Foo();
    Boo * boo = new Boo();
    Goo * goo = new Goo();

    foo->setCallback(goo, selector(Boo::callingFunc) );
    foo->doCall(); // outputs "Hey there" not "Yo there"
}

At this point, it kind of makes sense, yet it also kind of don't make sense. I mean, it's obvious that it's going to call "Hey There" from Boo, but why doesn't the code explode? It looks very dangerous.

EDIT 2: Found something really unsettling and disturbing. I adjusted the code so accept a counter so that I have a variable to check with what's happening.

#include <iostream>

using namespace std;

class Base
{
};

typedef void(Base::*handler)();
#define selector(_SELECTOR) static_cast<handler>(&_SELECTOR)

class Boo : public Base
{
public:
    Boo() : counter(0) {}
    void callingFunc()
    {
        cout << "Hey there " << counter << " at " << &counter << endl;
        counter += 1;
    }

    int counter;

};

class Goo : public Base
{
public:
    Goo() : counter(0) {}
    void callingFunc()
    {
        cout << "Yo there " << counter << " at " << &counter << endl;
        counter += 1;
    }

    int counter;
};

class Foo
{
public:
    void setCallback( Base * instance,  void (Base::*funcToCall)(void) )
    {
        this->instance = instance;
        this->funcToCall = funcToCall;
    }

    void doCall()
    {
        (instance->*funcToCall)();
    }

private:
    Base* instance;
    void (Base::*funcToCall)(void);
};

void main()
{
    Foo * foo = new Foo();
    Boo * boo = new Boo();
    Base * base = new Base();
    Goo * goo = new Goo();

    // first run
    foo->setCallback(goo, selector(Boo::callingFunc) );
    foo->doCall(); // "Hey there 0 at 0044BC60"

    foo->setCallback(boo, selector(Boo::callingFunc) );
    foo->doCall(); // "Hey there 0 at 0044BC00"

    //second run
    foo->setCallback(goo, selector(Boo::callingFunc) );
    foo->doCall(); // "Hey there 1 at 0044BC60"

    foo->setCallback(boo, selector(Boo::callingFunc) );
    foo->doCall(); // "Hey there 1 at 0044BC00"

    // attempt with base
    foo->setCallback(base, selector(Boo::callingFunc) );
    foo->doCall(); // "Hey there *rubbish number* at  at 0044BC30"

}

Now I am quite certain the function callback is a runtime thing (obviously it is since it doesn't give a compile error, but I wasn't sure because usually it isn't the case). If it is a runtime thing, then yes it kind of makes sense since it works almost like a scripting language (look for variable by its name, update it if it's there, etc).

I still need someone to confirm this. It really looks powerful and dangerous at the same time. It's been really a while since I have seen something like this. I'm too busy at the moment to try to open the thing in assembly to decipher what exactly is happening. Plus, I'm not good at reading it ^^;;

EDIT 3 Thanks guys it all makes sense now. villekulla's reply led me to believe that because my Boo and Goo classes are structured the same way, it is able to access the 'counter' variable the same way (should be obvious if you understand how classes' and structs' memories are allocated). So I slotted a 'char' variable at Goo:

class Goo : public Base
{
public:
    Goo() : counter(0) {}
    void callingFunc()
    {
        cout << "Yo there " << counter << " at " << &counter << endl;
        counter += 1;
    }

    char hey;
    int counter;
};

calling:

foo->setCallback(goo, selector(Boo::callingFunc) );
foo->doCall();

twice would yield gibberish since it is grabbing the char where the counter should be (confirming the undefined behaviour as noted). There is no compile error because...well...nothing is TERRIBLY wrong with the code as far as compiling is concerned.

Thanks again!

Upvotes: 3

Views: 365

Answers (2)

Evgeny Shavlyugin
Evgeny Shavlyugin

Reputation: 421

May be it becomes more clear if you consider how method call is implemented in the most of the modern C++ compilers. The non-virtual instance method is actually a static function that takes this pointer as the first argument. For example:

class A {
  void f();
}

is equivalent to

class A {
  static void f( A* this );
}

So when you are passing a pointer to an instance method, think of it as you are passing a pointer to a static function with first this argument like funcToCall( Base* this ). The problem is that the real implementation of funcToCall thinks that the data of your instance pointer respects to Foo class which may cause a lot of trouble if your classes has non-trivial data. This explains why your code works - it doesn't use an instance data at all.

For experiment you may try the following code:

Boo* boo = 0;
boo->funcToCall();

If you didn't catch a debug assertion, it will work since your method doesn't use any instance data.

Upvotes: 2

villekulla
villekulla

Reputation: 1085

It's undefined behavior.

Consider adding a member to both Foo and Goo and adjust callingFunc to use that member:

class Boo : public Base
{
public:
  Boo()
    : m("Boo")
  {}
  void callingFunc()
  {
     cout << "Hey there, I'am " << boo << endl;
  }
  const char* m;
};

class Goo : public Base
{
public:
  Goo()
    : m("Goo")
  {}
  void callingFunc()
  {
     cout << "Yo there, I'am " << goo << endl;
  }
  const char* m;
};

In the case

foo->setCallback(boo, selector(Boo::callingFunc) );

you get the output

Hey there, I'am Boo

and in the case

foo->setCallback(goo, selector(Boo::callingFunc) );

you get the output

Hey there, I'am Goo

You clearly see that Boo::callingFunc gets some intance of Goo...

This is one of the millions of examples how C++ helps shooting yourself in the foot... only doing things the Standard allows :/

Your example just didn't explode because callingFunc and Goo/Foo are trivial. And if you are unluckily it would never explode, it "only" would introduce strange bugs (Foo::callingFunc handling data of Goo::callingFunc).

As you didn't use virtual functions, all function call addresses are resolved on compile time when getting the address (in the line foo->setCallback(boo, selector(Boo::callingFunc) );

Upvotes: 3

Related Questions