如何绑定地址可能更改的实例的成员函数?

How can I bind a member function of an instance whose address may change?

#include <iostream>
#include <string>
#include <functional>
#include <vector>

class B;

class A {
public:
    std::function<void(B*, const std::string&)> m_callback;
    void* m_dest = nullptr; // Will be used in later versions of this code.
    void trigger(const std::string& message) {
        m_callback(m_dest, message);
    }
};

class B {
public:
    void callbackFunc(void* ignore, const std::string& message) {
        std::cout << message << std::endl;
    }
    void otherCallbackFunc(void* ignore, const std::string& message) {
        std::cout << "The other function was called!" << std::endl;
    }
};

int main {
    A a1, a2;
    B* b1 = new B();
    B* b2 = new B();

    a1.m_dest = (void *)b;
    a1.m_callback = std::bind(&B::callbackFunc, b, std::placeholders::_1, std::placeholders::_2);

    a2.m_dest = (void *)b;
    a2.m_callback = std::bind(&B::otherCallbackFunc, b, std::placeholders::_1, std::placeholders::_2);

    a1.trigger("Hello, world.");
    a2.trigger("Hello, world.");

    delete B;
}

以上代码有效。

但是B实例不是'static';如果它存储在向量或其他可能 std::move 的数据结构中?我如何重写 A,以便在为 B 提供最新的内存地址时,它仍然可以正确调用 callbackFunc()?

一种可能的解决方案是使用 lambda:

int main {
    A a1, a2;
    std::vector<B> bees = {};
    bees.push_back(B()); // b1
    bees.push_back(B()); // b2

    a1.m_callback = [](void* dest, const std::string& message){
        ((B*)dest)->callbackFunc(dest, message);
    };
    a2.m_callback = [](void* dest, const std::string& message){
        ((B*)dest)->otherCallbackFunc(dest, message);
    };

    /* move b instances about using std::move... */
    bees.insert(bees.begin(), B()); // b3

    // At this point in the program, we do not know which callback function was attached for each B instance.
    a1.m_b = (void *)&bees[0]; // b1 is now at a different address, but we still know where it is and can tell a1 that information.
    a2.m_b = (void *)&bees[1]; // b2 is now at a different address, but we still know where it is and can tell a2 that information.

    a1.trigger("Hello, world.");
    a2.trigger("Hello, world.");
}

然而,这太可怕了。它涉及创建潜在的大量几乎没有区别的 lambda,散布在任何可能想要将回调放入 A 的代码中。它丑陋、低效且使用起来很痛苦。

我怎样才能做得更好?如何绑定潜在移动实例的成员函数?

How can I bind a member function of an instance whose address may change?

严格来说,实例的地址不会改变。但是,我想你的意思是你可能希望在不修改仿函数的情况下将绑定实例替换为另一个实例。

if it's stored in a vector or some other data structure which may move it about?

这不能通过直接绑定实例来实现,因为那最终将是错误的实例。一种解决方案是间接的:绑定到指向实例的东西,并在替换旧实例时更新为指向新实例。

对于 vector 中的实例,一种解决方案是绑定 vector 本身。例如,假设您希望仿函数调用向量第一个元素的成员函数。重新分配后,元素将被存储在其他地方的另一个实例替换,但这并不重要,因为我们总是通过向量访问它来找到正确的元素:

std::vector<B> bs(42);

a.m_callback = [&bs](void* dest, const std::string& message) {
    bs[0].callbackFunc(dest, message);
}

解决该问题的另一种方法是确保元素的地址不变。基于节点的数据结构,例如链表 (std::list) 和树 (std::map) 为所有操作提供这种保证(出于显而易见的原因,删除元素除外)。