Reputation: 793
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
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
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
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