提问人:codesavesworld 提问时间:9/25/2023 最后编辑:Jan Schultkecodesavesworld 更新时间:9/26/2023 访问量:141
为什么在构造函数中调用虚拟方法并绑定虚拟方法,然后稍后调用它会产生不同的结果?
Why does invoking a virtual method in constructor and binding a virtual method then calling it later yield different results?
问:
这是我的代码片段:
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();
}
输出为:
base foo
derived foo
foo()
prints ,因为此时 vtable 仍然指向 ,根据这个问题下的答案。base foo
Base::foo
在构造过程中,对象还不是类。那么,调用时指针还是指向类的指针,而参数指针是在构造函数体中传递的,那么为什么要在类中调用呢?Base
Derived
std::bind()
this
Base
Base
p->fn
foo
Derived
我的编译器是 Apple clang 版本 14.0.3
答:
3赞
Drew Dormann
9/25/2023
#1
当调用 std::bind 时仍然是指向 Base 类的指针,并且参数指针是在 Base 的构造函数体中传递的,为什么 p->fn 在派生类中调用 foo?
调用构造函数后,指向 的每个指针现在都是指向 Derived
的指针。从该位置访问的 vtable 现在是派生的 vtableDerived
Base
这就是动态调度的“动态”部分。
评论
0赞
Ben Voigt
9/25/2023
更准确地说,每个指向的指针现在都是指向“子对象”的指针(它可能不包含Base
Base
Derived
Derived
)
0赞
Drew Dormann
9/25/2023
@codesavesworld vtable 指针在调用派生构造函数之前立即更改。
6赞
Ben Voigt
9/25/2023
#2
std::bind
(或指向成员函数的指针的任何其他用法,其中所讨论的成员函数是)不绑定到特定函数。相反,当与虚拟成员函数一起使用时,它会绑定到虚拟调度槽1(即使用 vtable 的常见实现上的 vtable 槽)。virtual
因此,在 vtable 被更多派生构造函数重新配置后,通过结果函子找到的函数会发生变化。bind
1 指针到成员函数类型很胖有几个原因,它包含的不仅仅是一个代码地址,并且需要能够虚拟调度就是其中之一。
2赞
molbdnilo
9/25/2023
#3
只是因为我有点无聊,并且为了表明不涉及魔法,这里有一个非常简化(和有效)的插图,说明事情在幕后是如何运作的(现实世界中还有很多事情发生,但原理是一样的):
#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();
}
评论
std::function
在这里并不重要。整个事情是由事实引起的,对象是如何一步一步地构建的。有 std::function,没有它。Derived
Base::foo()
Derived