简体   繁体   English

指向成员函数的函数指针映射

[英]Map of function pointers to member functions

I've tried various solutions on SO to solve this problem, yet I must be doing something wrong.我已经在 SO 上尝试了各种解决方案来解决这个问题,但我一定是做错了什么。

I have several classes where methods in each of the classes have the same method signature:我有几个类,其中每个类中的方法都具有相同的方法签名:

typedef int (*ControllerMethod)(const std::string &data, const std::unordered_map<std::string, std::string> &params);

And an example class having some method using that signature:还有一个示例类,它有一些使用该签名的方法:

class StaticContentController {
public:
    int handleStaticContentRequest(const std::string &data, const std::unordered_map<std::string, std::string> &params) {
        return 1;
    }
}

Now I try to create a map of pointers to member functions:现在我尝试创建一个指向成员函数的指针映射:

std::map<std::string, ControllerMethod> operations;
        operations.emplace("staticContent", &StaticContentController::handleStaticContentRequest);
std::string d("test.txt");
ControllerMethod f = operations["staticContent"];
auto s = ((_staticContentController).*f)(d, pooledQueries); // <- compile error here

but calling the method gives the compile error但是调用该方法会导致编译错误

Right hand operand to .* has non-pointer-to-member type 'web::server::ControllerMethod'

What am I missing?我错过了什么?

Update:更新:

I now have an empty Controller base class which other controller classes inherit from:我现在有一个空的 Controller 基类,其他控制器类继承自:

namespace web { namespace server {

    class Controller {

    };

    typedef ControllerResponse (Controller::*ControllerMethod)(const std::string &data, const std::unordered_map<std::string, std::string> &params);

}}

Now I'm getting the following error at operations.emplace():现在我在 Operations.emplace() 收到以下错误:

No matching constructor for initialization of 'std::__1::pair<const std::__1::basic_string<char>, web::server::ControllerResponse 

Updated answer更新答案

You're trying to use two different paradigms here, and they aren't really compatible with one another.您在这里尝试使用两种不同的范式,但它们实际上并不兼容。 If I interpret your edit correctly, you're trying to create a map of functions that call into other classes, and you want to declare this map as a set of function pointers.如果我正确解释了您的编辑,则您正在尝试创建调用其他类的函数映射,并且您希望将此映射声明为一组函数指针。

Function pointers are an assembly level construct, exposed by C. The syntax reflects this - and getting a C++ class to conform to this is not possible without help - namely, adding a context pointer parameter that is associated with every function pointer, and converting the context pointer to a class instance to call the member function.函数指针是一个汇编级构造,由 C 公开。语法反映了这一点——如果没有帮助,就不可能让 C++ 类符合这一点——即,添加一个与每个函数指针关联的上下文指针参数,并转换指向调用成员函数的类实例的上下文指针。

So, how do we fix the problem?那么,我们如何解决这个问题呢?

In both of the next approaches, we need a context object associated with the function table.在接下来的两种方法中,我们都需要一个与函数表关联的上下文对象。 This involves creating a structure to hold the member function and the context:这涉及创建一个结构来保存成员函数和上下文:

template<typename T> struct FunctionTableEntry
{
    ControllerMethod Function;
    T* Context;
};

and our function pointer becomes the following:我们的函数指针变成了以下内容:

typedef ControllerResponse (T::*ControllerMethod)(const std::string &data, const StringMap &params);

Here, StringMap is a typedef for std::unordered_map<std::string, std::string> .这里, StringMapstd::unordered_map<std::string, std::string>

Our main problem now comes with removing the template parameter T, as we can't make maps of runtime defined templates (a template who's type will only be known at run time).我们现在的主要问题是移除模板参数 T,因为我们无法制作运行时定义的模板的映射(模板的类型只能在运行时知道)。

There are two main approaches to take in resolving this, and both have issues that will need to be considered.有两种主要的方法可以解决这个问题,两者都有需要考虑的问题。 The first is to perform C style type erasure with pointers and very careful association.第一个是使用指针和非常小心的关联来执行 C 风格的类型擦除。 The second is to abandon function pointers in favor of C++ function objects.第二种是放弃函数指针,转而使用 C++ 函数对象。

C-Style Type Erasure C 型类型擦除

This option involves using C-style casts to convert the class instance pointer to its base class type, the member function pointer to the type expected by the function declaration, and then making the call as though the base class defines the method.此选项涉及使用 C 样式转换将类实例指针转换为其基类类型,将成员函数指针转换为函数声明所期望的类型,然后进行调用,就像基类定义方法一样。 This requires the use of pointers, and cannot be done without them.这需要使用指针,没有它们就无法完成。

To do this, our FunctionTableEntry structure changes to the following:为此,我们的FunctionTableEntry结构更改为以下内容:

struct FunctionTableEntry
{
    ControllerMethod Function;
    Controller* Context;
}

and our function pointer to:和我们的函数指针:

typedef ControllerResponse (Controller::*ControllerMethod)(const std::string &data, const StringMap &params);

To add a new entry, we do the following:要添加新条目,我们执行以下操作:

std::map<std::string, FunctionTableEntry> operations;
FunctionTableEntry Entry;
Entry.Function = (ControllerMethod)&StaticContentController::handleStaticContentRequest;
Entry.Context = (Controller*)&_staticContentController;
operations.emplace("staticContent", Entry);

And to call it:并称之为:

FunctionTableEntry f = operations["staticContent"];
auto s = ((f.Context)->*f.Function)(d, pooledQueries);

This method suffers from a few drawbacks - first, you have no other choice but to use pointers to refer to your controller objects - casting will not function properly otherwise.这种方法有一些缺点——首先,你别无选择,只能使用指针来引用你的控制器对象——否则转换将无法正常工作。 You can make this a bit more C++ friendly with std::shared_ptr, but otherwise, there is no way to replace it.您可以使用 std::shared_ptr 使其对 C++ 更加友好,但除此之外,无法替换它。 This also means you need to carefully manage the lifetime of your controller objects.这也意味着您需要仔细管理控制器对象的生命周期。 If they get freed while the function table is still referencing them you will almost certainly crash the system.如果在函数表仍在引用它们时释放它们,您几乎肯定会导致系统崩溃。

Second, the casting can cause issues with complex inheritance hierarchies.其次,转换可能会导致复杂继承层次结构的问题。 This method only works if (Controller*)_staticContentController == _staticContentController , ie casting to the base class gives the same numerical pointer value.此方法仅在(Controller*)_staticContentController == _staticContentController ,即转换到基类给出相同的数字指针值。 Otherwise, the called method will fail as it will not be able to properly reference its local data.否则,被调用的方法将失败,因为它将无法正确引用其本地数据。

This method has the advantage of being quite fast, however.然而,这种方法的优点是速度非常快。 There is no function overhead besides the table lookup, and the generated assembly is not much more than just calling the function normally.除了查表之外没有任何函数开销,生成的程序集也不过是正常调用函数而已。 It is also runtime independent - so long as the equality expression above is true with all users of the controller system, anyone with a C++ compiler can create a new controller and this system will be able to call their functions, even if they use a completely different runtime library.它也是独立于运行时的——只要上面的等式表达式对于控制器系统的所有用户都是真的,任何拥有 C++ 编译器的人都可以创建一个新的控制器,这个系统将能够调用他们的函数,即使他们使用完全不同的运行时库。

Additionally, if you know the controller instance is going to be used with multiple functions, you can modify the structure to provide a map of functions associated with one Context value, allowing you to reduce some of the memory overhead.此外,如果您知道控制器实例将与多个函数一起使用,您可以修改结构以提供与一个Context值关联的函数映射,从而减少一些内存开销。 This may not be possible with your design, but it's worth looking into if memory is a concern.这对于您的设计来说可能是不可能的,但如果内存是一个问题,则值得研究。

C++ Function Objects C++ 函数对象

The second solution is to completely do away with C-style function pointers altogether and use std::function .第二种解决方案是完全取消 C 风格的函数指针并使用std::function Since std::function can contain instance data as part of itself, and can be placed into a map, this allows you to std::bind a member function, creating a partially specified function call (I believe in functional programming this is what's called a closure).由于std::function可以包含实例数据作为其自身的一部分,并且可以放置到映射中,这允许您std::bind成员函数,创建部分指定的函数调用(我相信在函数式编程中这就是所谓的关闭)。

In this case, there is no FunctionTableEntry structure - instead we use the following:在这种情况下,没有FunctionTableEntry结构 - 相反,我们使用以下内容:

typedef std::function<ControllerResponse(const std::string&, const StringMap&)> ControllerMethod;

To add a new method, we do the following:要添加新方法,我们执行以下操作:

std::map<std::string, ControllerMethod> operations;
operations.emplace("staticContent", std::bind(&StaticContextController::handleStaticContentRequest, &_staticContentController, std::placeholders::_1, std::placeholders::_2);

This creates a closure that calls the member function with the required controller instance.这将创建一个闭包,它使用所需的控制器实例调用成员函数。

To call this, we do the following:要调用它,我们执行以下操作:

std::string d("test.txt");
ControllerMethod f = operations["staticContent"];
auto s = f(d, pooledQueries);

C++ function objects override operator () , which allows them to work as though they were static functions. C++ 函数对象覆盖operator () ,这允许它们像静态函数一样工作。

This method allows for both member functions and static functions to exist in the same map.此方法允许成员函数和静态函数存在于同一映射中。 It also allows for complex inheritance hierarchies to occur, as there is no casting to make things function - everything occurs with template functions.它还允许发生复杂的继承层次结构,因为没有强制转换来使事情发挥作用 - 一切都发生在模板函数中。

The downside to this method is you still need to deal with object lifespan - the content controller objects cannot be destroyed until after the function map has been cleared.这种方法的缺点是你仍然需要处理对象的生命周期——在清除函数映射之前,内容控制器对象不能被销毁。 In addition, there is some overhead due to the use of std::function with placeholder parameters (though that likely depends on the runtime library in use, my tests have shown it generates a whole lot more code in x86-64 GCC 9.3).此外,由于使用带有占位符参数的 std::function 会产生一些开销(尽管这可能取决于使用的运行时库,但我的测试表明它在 x86-64 GCC 9.3 中生成了更多代码)。

This method also is not runtime independent - whatever runtime you choose to use here must also be used by every programmer that uses this code, otherwise incompatibilities in the way each library creates and stores std::function will cause strange failures.这个方法也不是运行时独立的——你在此处选择使用的任何运行时也必须由使用此代码的每个程序员使用,否则每个库创建和存储std::function方式的不兼容性将导致奇怪的失败。 This means no compiler mixing - if you used MSVC 2019 to build the API, everyone else who uses this library must use MSVC2019 to build their controller component.这意味着没有编译器混合 - 如果您使用 MSVC 2019 来构建 API,则使用此库的其他所有人都必须使用 MSVC2019 来构建他们的控制器组件。 If you aren't providing an API here, then this is not an issue.如果您没有在此处提供 API,那么这不是问题。


Original answer原答案

Your function pointer declaration is wrong - pointers to members have a different syntax to the normal function pointer typedef.您的函数指针声明是错误的 - 成员指针与普通函数指针 typedef 的语法不同。

A normal function pointer uses the syntax you have currently:普通函数指针使用您当前的语法:

typedef int (*foo)(int x, int y);

A pointer to member function typedef looks like this:指向成员函数 typedef 的指针如下所示:

typedef int (SomeClass::*foo)(int x, int y);

The SomeClass:: section is required as pointers to members have an additional parameter to them, called this . SomeClass::部分是必需的,因为指向成员的指针有一个附加参数,称为this In C++, the this pointer is passed as the first argument to the function, which makes the function declaration different (as the actual assembly code needed to call the function is different, see MSVC generated assembly for a real world example).在 C++ 中, this指针作为第一个参数传递给函数,这使得函数声明有所不同(因为调用该函数所需的实际汇编代码不同,请参阅 MSVC 生成的汇编以获得真实示例)。

To solve the issue, you need to provide a base class that can be used to declare the typedef, then inherit from that class to allow the method to be called.为了解决这个问题,您需要提供一个可以用于声明typedef的基类,然后从该类继承以允许调用该方法。 This is effectively identical to using inheritance, unless you have multiple methods in the same type that have the same signature, but do different things.这实际上与使用继承相同,除非您在同一类型中有多个具有相同签名但做不同事情的方法。

The DirectX 11 Effects framework uses this exact paradigm to avoid branching when configuring different shader types in the graphics pipeline - see here , at line 590. DirectX 11 效果框架使用这种精确的范例来避免在图形管道中配置不同的着色器类型时出现分支 - 请参阅此处,第 590 行。

As pointed out, the type of a non-static member function of the class StaticContentController is not:正如所指出的,类StaticContentController的非静态成员函数的类型不是:

typedef int (*ControllerMethod)(const std::string &data, const std::unordered_map<std::string, std::string> &params);

Instead, it is:相反,它是:

typedef int (StaticContentController::*StaticContentControllerMethod)(const std::string &data, const std::unordered_map<std::string, std::string> &params);

This was your initial error.这是你最初的错误。

This makes sense as you need an instance to call the member function, and the instance has a type as well.这是有道理的,因为您需要一个实例来调用成员函数,并且该实例也有一个类型。 And it makes sense that if you have a Base::*Function pointer, you can call it with an instance of a class publicly and unambiguously derived from Base, because a derived pointer can be converted implicitly to a base pointer.如果你有一个Base::*Function指针,你可以用一个类的实例来调用它,并且明确地从 Base 公开且明确地派生出来,这是有道理的,因为派生的指针可以隐式地转换为基指针。

It also makes sense that you cannot assign a Derived::*Function pointer to a Base::*Function pointer because the result could be called with any Base instance, which need not be a Derived instance.不能将Derived::*Function指针分配给Base::*Function指针也是有道理的,因为可以使用任何Base实例调用结果,而不必是Derived实例。 This was the error in the question update.这是问题更新中的错误。

In this very limited circumstance, C++ behaves completely logically.在这种非常有限的情况下,C++ 的行为完全符合逻辑。

With the modification to the correct type, your snippet will compile:通过对正确类型的修改,您的代码段将编译:

std::map<std::string, StaticContentControllerMethod> operations;
operations.emplace("staticContent", 
    &StaticContentController::handleStaticContentRequest);
std::string d("test.txt");
StaticContentControllerMethod f = operations["staticContent"];
auto s = ((_staticContentController).*f)(d, pooledQueries); // <- works

So presumably your actual question is how to store in this map member function pointers for multiple classes and not just StaticContentController .所以大概你的实际问题是如何在这个映射中存储多个类的成员函数指针,而不仅仅是StaticContentController But that is the wrong question.但这是错误的问题。 You have to have the instance ( _staticContentController ) to invoke the member function pointer, so you already know the type.您必须拥有实例 ( _staticContentController ) 来调用成员函数指针,因此您已经知道类型。

So maybe you want to ask how to erase the type.所以也许你想问如何擦除类型。 One way is storing something that doesn't require an instance: for that, use std::function as the mapped type and bind the instance when inserting into the map.一种方法是存储不需要实例的东西:为此,使用std::function作为映射类型并在插入映射时绑定实例。 That would work and be straightforward if you have the controller at the time the map is created.如果您在创建地图时拥有控制器,这将起作用并且很简单。 A second way is using a type erasing type like std::any for the mapped type, and use any_cast at the point of use to return it to its initial type.第二种方法是对映射类型使用像std::any这样的类型擦除类型,并在使用点使用any_cast将其返回到其初始类型。 A third way is to use a common base class with virtual functions which are overridden in your classes.第三种方法是使用公共基类和在您的类中覆盖的虚函数。 Since the virtual functions can be called with a base pointer, you can store member function pointers of the base class.由于可以使用基指针调用虚函数,因此您可以存储基类的成员函数指针。

Alternatively, maybe you want to ask how to have a type-indexed collection: the type is known at lookup time (because you have an instance) and you need to lookup a value whose type (member function pointer) depends on the "key" type.或者,也许您想询问如何拥有类型索引集合:该类型在查找时已知(因为您有一个实例)并且您需要查找其类型(成员函数指针)取决于“键”的值类型。

The simplest way to do this is to have templated classes, and let the compiler handle the mapping:最简单的方法是使用模板化类,并让编译器处理映射:

template<typename T>
struct operations {
    static std::map<std::string, void (T::*)(etc.)> pointers;
};
// use:
operations<StaticContentController>::pointers["staticContent"];

Another version of type-indexing might have the following interface:另一个版本的类型索引可能具有以下接口:

template<template<typename> typename Value>
class type_keyed_map
{
public:
    template<typename T>
    void insert(std::unique_ptr<Value<T>> value);

    template<typename T>
    auto find() -> Value<T>*; // return null if not found
};

You can use a std::map in the implementation, but std::map does not allow multiple value types.您可以在实现中使用std::map ,但std::map不允许多个值类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM