将 std::bind 和 std::function 与 class 成员一起使用会导致回调使用旧对象引用?
Using std::bind and std::function with a class member causes the callback to use an old object reference?
这令人困惑,但基本上我得到的是一个 class,它具有使用 c++11 std::function
和 std::bind
的回调函数。一切正常。但是当我需要通过重新分配对象来重置所有内容时,新对象似乎并没有使所有引用都正确。这是一个演示我的问题的示例程序:
#include <functional>
#include <list>
#include <iostream>
#include <string>
class OtherClass
{
public:
OtherClass() = default;
void RegisterCallback(std::function<void(void)> f) {
callback = f;
}
void PrintThings() {
callback();
}
std::function<void(void)> callback;
};
class MyClass
{
public:
MyClass() {
list_of_things.push_back("thing1");
list_of_things.push_back("thing2");
list_of_things.push_back("thing3");
list_of_things.push_back("thing4");
other_class.RegisterCallback(std::bind(&MyClass::MyFunction, this));
}
void PrintThings() {
MyFunction();
other_class.PrintThings();
}
void MyFunction() {
auto a = this;
for (auto& thing: list_of_things)
{
std::cout << thing << std::endl;
}
}
OtherClass other_class;
std::list<std::string> list_of_things;
};
int main()
{
MyClass my_class;
my_class.PrintThings();
my_class = MyClass();
my_class.PrintThings();
std::cout << "done" << std::endl;
}
这有点令人困惑,但基本上如果你用调试标志编译并单步执行它,你会发现我们第一次调用 my_class.PrintThings()
时它打印了两次;一次用于 MyFunction()
调用,一次用于 other_class.PrintThings()
调用,后者调用 MyFunction
作为回调。然后,我们用 my_class = MyClass()
替换对象,调用一个新的构造函数等等。当我们单步执行时,我们发现它在调用 MyFunction
时打印列表,但在调用 other_class.PrintThings()
时打印 而不是 。 MyFunction
有一个变量 a
,我用它来查看对象的地址;第二次通过 a
具有不同的地址,具体取决于 MyFunction
是否作为来自 OtherClass
.
的回调被调用
在这个例子中,有问题的幽灵对象只有一个空列表(大概是被销毁的结果)但在我遇到这个的实际程序中,它被垃圾内存填满并导致了段错误。我还注意到我的调试器有一些奇怪的行为;当它到达幽灵对象时,它不会只是介入或越过,它会跳过该函数,除非我在那里放置一个断点。
这是怎么回事?为什么第二次没有正确绑定回调?我需要在析构函数中做些什么特别的事情吗?我是否缺少对函数指针或 std::bind
的一些基本理解?
What is going on?
未定义的行为
Why isn't the callback bound properly the second time through?
因为您在将新的 MyClass 分配给 my_class 的过程中创建了一个新的 OtherClass。你还没有初始化它的回调。
Is there something special I need to do in the destructor or something?
在析构函数、赋值和复制构造函数中。这是因为您自己存储了 "myself" 的地址。这一切都很好,直到对象改变地址,它会在复制时改变地址。请注意,在下面的代码中,所有这三个都通过使用 smart_ptr.
来处理
一种解决方案是重构 MyClass 以使用 pimpl 习惯用法。即 class 是一个地址永远不会改变的实现的包装器。
#include <functional>
#include <iostream>
#include <list>
#include <string>
#include <memory>
class OtherClass
{
public:
OtherClass() = default;
void RegisterCallback(std::function<void(void)> f) {
callback = f;
}
void PrintThings() {
callback();
}
std::function<void(void)> callback;
};
class MyClass
{
struct Impl
{
Impl()
{
list_of_things.push_back("thing1");
list_of_things.push_back("thing2");
list_of_things.push_back("thing3");
list_of_things.push_back("thing4");
}
void MyFunction()
{
for (auto& thing: list_of_things)
{
std::cout << thing << std::endl;
}
}
void PrintThings() {
MyFunction();
other_class.PrintThings();
}
OtherClass other_class;
std::list<std::string> list_of_things;
};
std::unique_ptr<Impl> impl_;
public:
MyClass()
: impl_(std::make_unique<Impl>())
{
impl_->other_class.RegisterCallback(std::bind(&Impl::MyFunction, impl_.get()));
}
void PrintThings() {
impl_->PrintThings();
}
};
int main()
{
MyClass my_class;
my_class.PrintThings();
my_class = MyClass();
my_class.PrintThings();
std::cout << "done" << std::endl;
}
预期输出:
thing1
thing2
thing3
thing4
thing1
thing2
thing3
thing4
thing1
thing2
thing3
thing4
thing1
thing2
thing3
thing4
done
这令人困惑,但基本上我得到的是一个 class,它具有使用 c++11 std::function
和 std::bind
的回调函数。一切正常。但是当我需要通过重新分配对象来重置所有内容时,新对象似乎并没有使所有引用都正确。这是一个演示我的问题的示例程序:
#include <functional>
#include <list>
#include <iostream>
#include <string>
class OtherClass
{
public:
OtherClass() = default;
void RegisterCallback(std::function<void(void)> f) {
callback = f;
}
void PrintThings() {
callback();
}
std::function<void(void)> callback;
};
class MyClass
{
public:
MyClass() {
list_of_things.push_back("thing1");
list_of_things.push_back("thing2");
list_of_things.push_back("thing3");
list_of_things.push_back("thing4");
other_class.RegisterCallback(std::bind(&MyClass::MyFunction, this));
}
void PrintThings() {
MyFunction();
other_class.PrintThings();
}
void MyFunction() {
auto a = this;
for (auto& thing: list_of_things)
{
std::cout << thing << std::endl;
}
}
OtherClass other_class;
std::list<std::string> list_of_things;
};
int main()
{
MyClass my_class;
my_class.PrintThings();
my_class = MyClass();
my_class.PrintThings();
std::cout << "done" << std::endl;
}
这有点令人困惑,但基本上如果你用调试标志编译并单步执行它,你会发现我们第一次调用 my_class.PrintThings()
时它打印了两次;一次用于 MyFunction()
调用,一次用于 other_class.PrintThings()
调用,后者调用 MyFunction
作为回调。然后,我们用 my_class = MyClass()
替换对象,调用一个新的构造函数等等。当我们单步执行时,我们发现它在调用 MyFunction
时打印列表,但在调用 other_class.PrintThings()
时打印 而不是 。 MyFunction
有一个变量 a
,我用它来查看对象的地址;第二次通过 a
具有不同的地址,具体取决于 MyFunction
是否作为来自 OtherClass
.
在这个例子中,有问题的幽灵对象只有一个空列表(大概是被销毁的结果)但在我遇到这个的实际程序中,它被垃圾内存填满并导致了段错误。我还注意到我的调试器有一些奇怪的行为;当它到达幽灵对象时,它不会只是介入或越过,它会跳过该函数,除非我在那里放置一个断点。
这是怎么回事?为什么第二次没有正确绑定回调?我需要在析构函数中做些什么特别的事情吗?我是否缺少对函数指针或 std::bind
的一些基本理解?
What is going on?
未定义的行为
Why isn't the callback bound properly the second time through?
因为您在将新的 MyClass 分配给 my_class 的过程中创建了一个新的 OtherClass。你还没有初始化它的回调。
Is there something special I need to do in the destructor or something?
在析构函数、赋值和复制构造函数中。这是因为您自己存储了 "myself" 的地址。这一切都很好,直到对象改变地址,它会在复制时改变地址。请注意,在下面的代码中,所有这三个都通过使用 smart_ptr.
来处理一种解决方案是重构 MyClass 以使用 pimpl 习惯用法。即 class 是一个地址永远不会改变的实现的包装器。
#include <functional>
#include <iostream>
#include <list>
#include <string>
#include <memory>
class OtherClass
{
public:
OtherClass() = default;
void RegisterCallback(std::function<void(void)> f) {
callback = f;
}
void PrintThings() {
callback();
}
std::function<void(void)> callback;
};
class MyClass
{
struct Impl
{
Impl()
{
list_of_things.push_back("thing1");
list_of_things.push_back("thing2");
list_of_things.push_back("thing3");
list_of_things.push_back("thing4");
}
void MyFunction()
{
for (auto& thing: list_of_things)
{
std::cout << thing << std::endl;
}
}
void PrintThings() {
MyFunction();
other_class.PrintThings();
}
OtherClass other_class;
std::list<std::string> list_of_things;
};
std::unique_ptr<Impl> impl_;
public:
MyClass()
: impl_(std::make_unique<Impl>())
{
impl_->other_class.RegisterCallback(std::bind(&Impl::MyFunction, impl_.get()));
}
void PrintThings() {
impl_->PrintThings();
}
};
int main()
{
MyClass my_class;
my_class.PrintThings();
my_class = MyClass();
my_class.PrintThings();
std::cout << "done" << std::endl;
}
预期输出:
thing1
thing2
thing3
thing4
thing1
thing2
thing3
thing4
thing1
thing2
thing3
thing4
thing1
thing2
thing3
thing4
done