通过地址调用成员函数(C++反射尝试)

Calling a member function by address (C++ Reflection attempt)

问题描述:

您好。 我正在为 C++ 实现自己的反射系统。我想制作一个简化的系统,您可以在其中查找哈希表中的方法并通过模板调用它。

我试图解决这个问题是提供一个包含有关方法的信息的元 class。这些信息是方法的地址和方法所属的Type以及名称。 class还提供了Invoke方法,这是一个模板方法,用户可以根据原型定义参数类型。

问题是我目前无法找到一种方法来将方法的地址传递到参数中,以满足 C++ 规则并保持代码的可移植性。

当前解决方案

#pragma once
#include <string>

class Primitive
{
private:
    std::string name;
public:
    Primitive(std::string name);
};


template<class Type>
class ClassMethod : Primitive
{
private:
    void* methodAddress;
    Type* targetClass;
public:
    ClassMethod(std::string name, Type* instance, void* methodAddress);
    template<typename ReturnType, typename ... Args>
    ReturnType Invoke(Args ... args);
};

template<class Type>
ClassMethod<Type>::ClassMethod(std::string name, Type* instance, void* methodAddress) : Primitive(name)
{
    targetClass = instance;
    this->methodAddress = methodAddress;
}

template<class Type>
template<typename ReturnType, typename ...Args>
inline ReturnType ClassMethod<Type>::Invoke(Args ... args)
{
    ReturnType(*funcionPointer)(Args ...) = (ReturnType(*) (Args ...))methodAddress;
    return (*targetClass.*funcionPointer)(args ...);
}

那么问题就出现在这里:

SimpleBorder::SimpleBorder() : renderBehavior(*this), setColorMethod(std::string("SetColor"), this, (void*)(&SimpleBorder::SetColor))
{
    brush = new Gdiplus::SolidBrush(Gdiplus::Color::White);
    pen = new Gdiplus::Pen(brush);
    pen->SetWidth(0.01f);
}


void SimpleBorder::SetColor(Gdiplus::Color color)
{
    this->color = color;
    pen->SetColor(color);
}

Gdiplus::Color SimpleBorder::GetColor()
{
    return color;
}

void SimpleBorder::SetBorderStyle(Gdiplus::DashStyle style)
{
    pen->SetDashStyle(style);
}

void SimpleBorder::SetThickness(float thickness)
{
    pen->SetWidth(thickness / 100);
}

void SimpleBorder::OnRender(RenderEventInfo e)
{
    e.GetGraphics()->DrawRectangle(pen, 0.0f, 0.0f, 1.0f, 1.0f);
    renderBehavior.OnRender(e);
}

void SimpleBorder::Repaint()
{

}

void SimpleBorder::AddRenderable(Renderable& renderable)
{
    renderBehavior.AddRenderable(renderable);
}

void SimpleBorder::RemoveRenderable(Renderable& renderable)
{
    renderBehavior.RemoveRenderable(renderable);
}

std::vector<std::reference_wrapper<Renderable>> SimpleBorder::GetRenderables()
{
    return renderBehavior.GetRenderables();
}

void SimpleBorder::SetProperty(std::string propertyName, std::string values ...)
{
}

bool SimpleBorder::HasProperty(std::string propertyName)
{
    return false;
}


声明

class SimpleBorder : public Renderable, public AccessTool
{
private:
    //Field map
    Gdiplus::SolidBrush* brush;
    Gdiplus::Pen* pen;
    DefaultRender renderBehavior;
    Gdiplus::Color color;

public:
    SimpleBorder(); 
    ~SimpleBorder();
    void SetColor(Gdiplus::Color color);
    Gdiplus::Color GetColor();
    void SetBorderStyle(Gdiplus::DashStyle style);
    void SetThickness(float thickness);

    // Inherited via Renderable
    virtual void OnRender(RenderEventInfo e) override;
    virtual void Repaint() override;
    virtual void AddRenderable(Renderable& renderable) override;
    virtual void RemoveRenderable(Renderable& renderable) override;
    virtual std::vector<std::reference_wrapper<Renderable>> GetRenderables() override;

    // Inherited via Renderable
    virtual void SetProperty(std::string propertyName, std::string values ...) override;
    virtual bool HasProperty(std::string propertyName) override;
    virtual std::any GetProperty(std::string propertyName) override;
};

解决方案说明:

如您所见,模板 ClassMethod 的想法是完全基于它所属的 class 的类型,以便能够将其保存在 std::unordered_map<std::string, ClassMethod<SimpleBorder>> methodTable 中 然后像这样调用:

methodTable["SetColor"].Invoke(Gdiplus::Color::Red)

这就是为什么我避免使用 class 模板来定义方法参数的原因,因为它会在方法之间产生紧密耦合,并使其无法保存在映射中。

想法是映射将包含方法,用户的工作是获取正确的参数并将它们插入 Invoke。

我试过的:

Invoke 中的代码或多或少只是一次失败的尝试。我用静态函数测试了它并且它有效。问题出现在成员函数中,因为它们的保存方式不同。我在其他问题上发现了一些 hack,但问题是这些解决方案不符合 C++ 标准,它们可以随时停止工作。

我需要:

我想要的要么是一个可以向我展示或告诉我一种不同的方法的答案,它也可以与成员函数一起使用,要么是一个可以编辑我拥有的解决方案的答案,以一种可行的方式不违反 C++ 标准。

抓住这个:

#include <string>

class Primitive
{
private:
    std::string name;
public:
    Primitive(std::string name) : name(name) {}
};

template<typename T>
class ClassMethod : Primitive
{
private:
    void (T::*methodAddress)(); // used as a "generic" function pointer
    T& targetClass;
public:
    template<typename R, typename ...Args> // no need for casting to void*
    // and also check type - `methodAddress` is a member function specifically on `T`, not any abstract pointer...
    ClassMethod(std::string name, T& instance, R (T::*methodAddress)(Args...)) :
        Primitive(name),
        // take a reference, unless you accept `nullptr`... I do not believe you do...
        targetClass(instance),
        // I do not believe this is very valid, but "will work".
        methodAddress(reinterpret_cast<void(T::*)()>(methodAddress))
        {
            // all those out of class template deifinitions are confusing
            // let's move them inside!
        }

    template<typename ReturnType, typename ...Args>
    ReturnType Invoke(Args... args)
    {
        ReturnType (T::*funcionPointer)(Args...) = reinterpret_cast<ReturnType (T::*)(Args...)>(methodAddress);
        // forward arguments
        return (targetClass.*funcionPointer)(std::forward<Args...>(args)...);
        // when targetClass is a pointer, then
        // `*targetClass.*funcionPointer` is equal to `*(targetClass.*funcionPointer)` !!
        // you want `((*targetClass).*functionPointer)` or just `targetClass->*funcionPointer
    }
};

// abstract example

#include <iostream>
#include <vector>

struct A {
    double func(int a) {
        std::cout << "func(" << a << ") ";
        return 3.41;
    }
    int fun2(char b) {
        std::cout << "fun2('" << b << "') ";
        return 50;
    }
};

int main() {
    A a;
    std::vector<ClassMethod<A>> v;
    v.emplace_back("func", a, &A::func);
    v.emplace_back("fun2", a, &A::fun2);
    auto rd = v[0].Invoke<double>(5); // you have to specify return type
    auto ri = v[1].Invoke<int>('a'); // you have to specify return type
    std::cout << rd << " " << ri << "\n";
}

tested on godbold prints:

func(5) fun2('a') 3.41 50

class ClassMethod 定义之外的函数定义令人困惑,还有那些 template<> template<> 东西。只需将它们放在 class 定义中即可。

I tested this with static functions and it worked.

作为对遵循 C++ 标准并避免黑客攻击感兴趣的人,您应该知道“它有效”并不意味着“它在不违反 C++ 标准的情况下有效”。在这种情况下,您已经从指向函数的指针中获得了指向 void 的指针。您通过 C-style cast expression, which in this case follows the rules for reinterpret_cast. The relevant rule is #8 in cppreference's list(添加了重点)做到了这一点:

  1. On some implementations (in particular, on any POSIX compatible system as required by dlsym), a function pointer can be converted to void* or any other object pointer, or vice versa. If the implementation supports conversion in both directions, conversion to the original type yields the original value, otherwise the resulting pointer cannot be dereferenced or called safely.

该标准确实支持您当前的静态成员函数方法,但仅限于某些系统。如果您担心编译器升级后代码中断,您的方法可能没问题。但是,如果您担心跨系统的可移植性,那么您最初的方法已经存在缺陷。

将方法扩展到非静态成员函数问题更大。标准为指向成员函数的指针可以转换成什么提供的选项更加有限:任何指向成员函数的指针类型(第 10 点,对于那些仍在阅读 cppreference 页面的人)。类型擦除不是一种有前途的方法(尽管它可以像另一个答案一样完成)。我倾向于尝试使用函数对象(函数)而不是函数指针,但这将是另一个答案的主题。

根据经验,如果您引入 void* 类型的内容,您的方法可能更多地来自 C 思维方式而不是 C++ 思维方式。