std::bind 会丢弃 C++11 中参数的类型信息吗?
Does std::bind discard type information of parameters in C++11?
问题发生案例
请考虑以下 C++ 代码:
#include <functional>
#include <iostream>
#include <string>
// Superclass
class A {
public:
virtual std::string get() const {
return "A";
}
};
// Subclass
class B : public A {
public:
virtual std::string get() const {
return "B";
}
};
// Simple function that prints the object type
void print(const A &instance) {
std::cout << "It's " << instance.get() << std::endl;
}
// Class that holds a reference to an instance of A
class State {
A &instance;
public:
State(A &instance) : instance(instance) { }
void run() {
// Invokes print on the instance directly
print(instance);
// Creates a new function by binding the instance
// to the first parameter of the print function,
// then calls the function.
auto func = std::bind(&print, instance);
func();
}
};
int main() {
B instance;
State state(instance);
state.run();
}
在这个例子中,我们有两个 classes A
和 B
。 B
继承自 class A
。两个 classes 都实现了一个简单的虚拟方法,returns 类型名称。
还有一个简单的方法,print
,它接受对 A
实例的引用并打印类型。
class State
持有对 A
实例的引用。 class 还有一个简单的方法,可以通过两种不同的方式调用 print
。
奇怪的地方
状态中的唯一方法首先直接调用print
。由于我们在 main 方法中提供了一个 B
int 实例,因此输出为 It's B
,正如预期的那样。
然而,对于第二次调用,我们使用 std::bind
将实例绑定到 print
的第一个参数。然后我们在不带任何参数的情况下调用结果函数。
然而,在这种情况下,输出是 It's A
。我会像以前一样期待输出 It's B
,因为它仍然是同一个实例。
如果我将参数声明为指针而不是引用,std::bind
将按预期工作。我还在两个 classes 的构造函数中放置了一些日志记录,以验证没有意外创建实例。
为什么会这样?在这种情况下 std::bind
会丢弃一些类型信息吗?据我了解,这一定不会发生,因为方法调用应该通过运行时的 vtable 查找来管理。
这只是 object slicing。通过引用传递实例:
auto func = std::bind(&print, std::ref(instance));
// ^^^^^^^^
再解释一下:与大多数 C++ 标准库类型一样,bind
表达式的结果类型 拥有 所有其绑定状态。这意味着您可以获取此值并自由传递它并存储它并稍后在不同的上下文中返回它,并且您仍然可以在所有绑定状态准备好操作时调用它。
因此,在您的代码中,绑定对象是使用 instance
的 copy 构造的。但是由于 instance
不是一个完整的对象,所以你导致了切片的发生。
相比之下,我的代码将 std::reference_wrapper<A>
复制到绑定对象中,这实际上是一个指针。它不拥有实例对象,所以只要绑定对象可能被调用,我就需要让它保持活动状态,但这意味着绑定调用以多态方式分派给完整对象。
问题发生案例
请考虑以下 C++ 代码:
#include <functional>
#include <iostream>
#include <string>
// Superclass
class A {
public:
virtual std::string get() const {
return "A";
}
};
// Subclass
class B : public A {
public:
virtual std::string get() const {
return "B";
}
};
// Simple function that prints the object type
void print(const A &instance) {
std::cout << "It's " << instance.get() << std::endl;
}
// Class that holds a reference to an instance of A
class State {
A &instance;
public:
State(A &instance) : instance(instance) { }
void run() {
// Invokes print on the instance directly
print(instance);
// Creates a new function by binding the instance
// to the first parameter of the print function,
// then calls the function.
auto func = std::bind(&print, instance);
func();
}
};
int main() {
B instance;
State state(instance);
state.run();
}
在这个例子中,我们有两个 classes A
和 B
。 B
继承自 class A
。两个 classes 都实现了一个简单的虚拟方法,returns 类型名称。
还有一个简单的方法,print
,它接受对 A
实例的引用并打印类型。
class State
持有对 A
实例的引用。 class 还有一个简单的方法,可以通过两种不同的方式调用 print
。
奇怪的地方
状态中的唯一方法首先直接调用print
。由于我们在 main 方法中提供了一个 B
int 实例,因此输出为 It's B
,正如预期的那样。
然而,对于第二次调用,我们使用 std::bind
将实例绑定到 print
的第一个参数。然后我们在不带任何参数的情况下调用结果函数。
然而,在这种情况下,输出是 It's A
。我会像以前一样期待输出 It's B
,因为它仍然是同一个实例。
如果我将参数声明为指针而不是引用,std::bind
将按预期工作。我还在两个 classes 的构造函数中放置了一些日志记录,以验证没有意外创建实例。
为什么会这样?在这种情况下 std::bind
会丢弃一些类型信息吗?据我了解,这一定不会发生,因为方法调用应该通过运行时的 vtable 查找来管理。
这只是 object slicing。通过引用传递实例:
auto func = std::bind(&print, std::ref(instance));
// ^^^^^^^^
再解释一下:与大多数 C++ 标准库类型一样,bind
表达式的结果类型 拥有 所有其绑定状态。这意味着您可以获取此值并自由传递它并存储它并稍后在不同的上下文中返回它,并且您仍然可以在所有绑定状态准备好操作时调用它。
因此,在您的代码中,绑定对象是使用 instance
的 copy 构造的。但是由于 instance
不是一个完整的对象,所以你导致了切片的发生。
相比之下,我的代码将 std::reference_wrapper<A>
复制到绑定对象中,这实际上是一个指针。它不拥有实例对象,所以只要绑定对象可能被调用,我就需要让它保持活动状态,但这意味着绑定调用以多态方式分派给完整对象。