c ++如何将一个通用函数与多个地图对象绑定并将其称为清除函数

c++ how to bind a general function with multiple map object and call it clear function

我的系统定义了多个类似unordered_map的数据,例如

      std::unordered_map< int,  int>  int_map;
      std::unordered_map< int,  double>  double_map;
      std::unordered_map< string,  string>  string_map;
      std::unordered_map< string,  string>  string_map_2;
      ...
      std::unordered_map< string, vector<string> >  string_string_map;
      std::unordered_map< string, vector<string> >  string_string_map_2;

每个地图包含不同的数据类型。这些与注册回调函数的框架(用 C 编写)一起使用。注册函数 (register_undo_handler) 有两个参数:指向回调函数的指针和要传递给回调的数据。在我们的例子中,数据是一个映射指针。

    //register our callback function to UNDO framework
    register_undo_handler(clear_data, &int_map);
    //or :
    register_undo_handler(clear_data, &string_map);

    // this is a sketch of the callback function, which frees the map resources
    void clear_data(void *data)
    {  
       data->clear();   
    }

如果UNDO发生,UNDO框架将调用每个定义的回调函数,例如clear_data,并传递注册数据,转换为void*,作为这样的参数

   // For each registered callback function, run with registered parameter
   (*registered_callback_func)(data); 

我们想要一个可以释放任何地图的回调函数。 对实施有何建议?

编辑: 我只是意识到如果只是清除那些地图,它并不比 更容易(这仍然需要完成)...我将把它留在这里作为 general-propose 处理程序到 C 处理程序解决方案的参考. (顺便说一句,技术上它只需要一个回调函数(第一个参数))

首先,我强烈建议迁移到 c++ 撤消框架


但是您实际上可以使用单个处理程序(第一个参数)将其归档。

重点是使用 type-erased 处理程序(下面代码中的std::function)作为数据(第二个参数)传递,并在包装​​处理程序(第一个参数)中调用它


注意:你还没有添加 register_undo_handler 的签名所以我猜是

void register_undo_handler(void(*)(void), void*);

代码(Wandbox

#include <functional>
#include <list>
#include <stack>
#include <iostream>
using namespace std;

void register_undo_handler(void(*callback)(void*), void* data){
    callback(data); //I'd not gonna implement all undo in C so let me do this instead
}

void the_callback_wrapper(void* cb){
    (*static_cast<function<void()>*>(cb))();
}

int main(){
    list<function<void()>> undos;
    //and your maps, they should have same lifetime
    
    int a=2,b=3;
    
    //do something...
    undos.emplace_back([]{std::cout << "I'd like to undo this - 1\n";}); 
    register_undo_handler(the_callback_wrapper, (void*)(&undos.back()));
    
    //undo with local variables, no problem
    
    undos.emplace_back([&]{std::cout << "I'd like to undo this - " << a << '\n';});
    register_undo_handler(the_callback_wrapper, (void*)(&undos.back()));
    
    undos.emplace_back([&]{std::cout << "I'd like to undo this - " << b << '\n';});
    register_undo_handler(the_callback_wrapper, (void*)(&undos.back()));
    
    //modify local variable, no problem
    
    undos.emplace_back([&]{std::cout << "I'd like to modify a to 100\n"; a=100;});
    register_undo_handler(the_callback_wrapper, (void*)(&undos.back()));
    std::cout << "a should be 100 : a = " << a << '\n';

    //use multiple local variable, no problem
    
    undos.emplace_back([&]{std::cout << "I'd like to modify **both a and b** to 10000\n"; a=b=100000;});
    register_undo_handler(the_callback_wrapper, (void*)(&undos.back()));
    std::cout << a << ' ' << b;
}

旁注:如果您可以使用全局变量来存储其撤消仿函数,您也可以将它们的索引作为数据传递并让回调决定调用哪个仿函数。


如果您只是不想重复代码,您可以使用单个 模板 来完成。

#include <functional>
#include <unordered_map>
#include <iostream>
using namespace std;

void register_undo_handler(void(*callback)(void*), void* data){
    callback(data); //I'd not gonna implement all undo in C so let me do this instead
}

template<typename clearable>
void Erase(void* data){
    ((clearable*)data)->clear();
}

int main(){
   std::unordered_map<int,  int>  int_map;
   std::unordered_map<int,  double>  double_map;
    
   double_map[0] = int_map[0] = 0;
   
   //you can wrap this into a function to avoid type types yourself
   register_undo_handler(&Erase<decltype(int_map)>, (void*)&int_map);
   register_undo_handler(&Erase<decltype(double_map)>, (void*)&double_map);
   
   std::cout << int_map.size() << ' ' << double_map.size();
}

调用“撤消”回调时,有两条信息可用,即调用函数的地址和提供给函数的 pointer-sized 数据。您的要求是第一部分不发生变化,因此第二部分需要告诉我们所涉及的地图类型。一个大问题是,一旦将指针转换为 void*,就无法再恢复其原始类型。因此,为了实现既定目标,您需要一种方法来使用原始指针以外的其他东西来识别映射。一种可能性是将指针包装在一个可以记住原始类型的对象中。这可能会变得混乱,尤其是当您需要动态分配这些包装器对象时。对此进行编码的开销比编写多个回调函数要多。同样,运行时开销也没有节省(时间和 space)。这可能不是你想要做的。

为每个类型写一个回调就简单多了。只需几行代码即可定义回调。每个函数编译成少量的目标代码;总而言之,编译函数几乎是任何解决方案所需的理论最小目标代码量。 (每个映射类型的 clear 函数都是一个不同的函数,因此它需要不同的函数调用才能调用它们中的每一个。我超过了最小值,因为我检查了 null。)

template <class T>
void call_clear(void * ptr)
{
    if ( ptr )
        static_cast<T*>(ptr)->clear();
}

注册其中一个回调在语法上有点麻烦(例如 register_undo_handler( call_clear<decltype(int_map)>, &int_map);),因此可能需要编写一个包装器来处理注册。这个包装器可以推导模板参数,减少重复输入的数量。编译器应在任何优化级别内联此函数,从而不会产生额外的运行时开销。

template <class T>
void register_undo_call_clear(T & map)
{
    register_undo_handler(call_clear<T>, &map);
}

至此,回调注册就完成了,例如register_undo_call_clear(int_map);.