codesavesworld
codesavesworld

Reputation: 793

Why does invoking a virtual method in constructor and binding a virtual method then calling it later yield different results?

This is my code snippet:

class Base {
  public:

  Base() {
    foo();
    bind();
  }

  virtual void foo() {
    std::cout << "base foo\n";
  }

  void bind() {
    fn = std::bind(&Base::foo, this);
  };

  std::function<void()> fn;
};

class Derived : public Base {
  public:

  void foo() override {
    std::cout << "derived foo\n";
  }

  void bind()  {
  }

  int val;    
};

int main() {
  Base* p = new Derived();
  p->fn();
}

The output is:

base foo
derived foo

foo() prints base foo, because at this point, the vtable still points to Base::foo, according to answers under this question.

During the construction of Base, the object is not yet of Derived class. So, when calling std::bind() the this pointer is still a pointer to the Base class, and the argument pointer is passed in the constructor body of Base, so why does p->fn call foo in the Derived class?

My compiler is Apple clang version 14.0.3

Upvotes: 7

Views: 167

Answers (3)

molbdnilo
molbdnilo

Reputation: 66371

Just because I was a bit bored, and to show that there is no magic involved, here is a very simplified (and working) illustration of how things can work behind the scenes (there is a lot more going on in the real world, but the principle is the same):

#include <iostream>

struct Base;

// Roll our own "bind + std::function" simplification.
struct Bind {
    Base* b;   // "this"
    size_t fn; // Index in function table.
    void operator()();
};

using member_fn = void(*)(Base*); // Limit to one type of member function.

struct Base
{
    member_fn* v_table;
    Bind fn;
};

void Bind::operator()() { b->v_table[fn](b); }

struct Derived : Base {};

void Base_foo(Base* self) { std::cout << "base foo\n"; }
void Base_bind(Base* self) { self->fn = { self, 0 }; } // foo is the first function.
member_fn Base_vtable[] = { Base_foo, Base_bind };

void Derived_foo_impl(Derived* self) { std::cout << "derived foo\n"; }
void Derived_foo(Base* self) { Derived_foo_impl(static_cast<Derived*>(self)); }
void Derived_bind_impl(Base* self) {}
void Derived_bind(Base* self) { Derived_bind_impl(static_cast<Derived*>(self)); }
member_fn Derived_vtable[] = { Derived_foo, Derived_bind };

void init_base(Base* self)
{
    self->v_table = Base_vtable;
    self->v_table[0](self); // foo()
    self->v_table[1](self); // bind()
}

void init_derived(Derived* self)
{
    init_base(static_cast<Base*>(self));
    self->v_table = Derived_vtable; // Now we are not a Base any more...
}

int main()
{
    Derived d;
    init_derived(&d);
    Base* b = static_cast<Base*>(&d);
    b->fn();
}

Upvotes: 2

Ben Voigt
Ben Voigt

Reputation: 283614

std::bind (or any other usage of pointer-to-member-function where the member function in question is virtual) doesn't bind to a particular function. Rather when used with a virtual member function, it binds to a virtual dispatch slot1 (i.e. vtable slot on common implementations which use a vtable).

So after the vtable is reconfigured by more derived constructors, the function found through the bind result functor changes.


1 There are a few reasons that pointer-to-member-function types are fat, containing more than just a code address, and the need to be able to virtually dispatch is one of them.

Upvotes: 6

Drew Dormann
Drew Dormann

Reputation: 63694

when called std::bind is still a pointer to Base class, and the argument pointer is passed in constructor body of Base, why p->fn called foo in derived class?

Once your Derived constructor is called, every pointer to the Base is now a pointer to Derived. The vtable accessed from that location is now a Derived vtable

That's the "dynamic" part of dynamic dispatch.

Upvotes: 3

Related Questions