【问题标题】:Casting issue when trying to use pointers to member functions尝试使用指向成员函数的指针时出现强制转换问题
【发布时间】:2012-05-04 08:57:12
【问题描述】:

出于多种原因,我必须使用在外部 C 库中定义的结构。我已经简化了我的代码以使其易于阅读。

C Lib 中定义的结构体

extern "C" {
    typedef struct {
        double (*_function)(double x);
    } FuncR;
}

包含 A 类的 Cpp 文件

Class A {
public:
    A();
    void foo(double x); // Cannot be static since it uses m
    void bar();
private:
    double m; // non-static member by nature
};

void A::bar() {
    FuncR f;
    f._function = &A::foo;
};

调用f._function = &A::foo; 产生以下错误:

error C2440 : '=' : cannot convert from 'double (__thiscall A::*)(double)' to 'double(__cdecl*)(double)'

我一直在寻找答案,显然foo必须声明为静态的。就我而言,这是不可能的,因为它必须使用非静态成员......

有什么技巧可以解决我的问题吗?

【问题讨论】:

  • 好的,extern C lib 中定义的结构不能更改,你必须使用它,我不知道为什么。谁使用结构中的 funcptr 来实际进行函数调用、您的新 C++ 代码或外部 C 库中的某些内容?
  • 如果您阅读了我的原始答案并将其视为“错误”,请阅读我对其所做的补充以使其正确!谢谢!

标签: c++ casting struct static-methods member-function-pointers


【解决方案1】:

不,我不认为有“解决”这个问题的技巧。

方法调用需要this指针,函数指针无法处理。

通常你可以定义一个静态的“trampoline”函数来让你进入一个方法,但这需要外层(在这种情况下是 C 代码)支持传递,例如一个void *,您可以在其中存储this 指针。

【讨论】:

    【解决方案2】:

    FuncR 结构真的需要在 C 代码中定义/使用吗?可以使用 C++ 风格的成员函数指针吗?

    这个怎么样?...

    class A;
    
    struct classA_MemberFuncR {
        double(A::*_function)(double);
    };
    
    class A {
    public:
        A() : m(1.234) {};
        double foo(double x) {
            std::cout << "In foo(" << x << "): this="
                    << (void*)this << ", m=" << m << '\n';
            return m+x;
        }
        void bar();
    private:
        double m; // non-static member by nature
    };
    
    void A::bar() {
        classA_MemberFuncR fm;
        fm._function = &A::foo;
        double result = (this->*fm._function)(4.321);
        std::cout << "Result is: " << result << std::endl;
    };
    

    [此时添加的评论:]当。重新阅读OP的原始帖子。 (假设我们被 C 结构的非成员函数指针卡住了。)

    嗯。在 GCC 上,我尝试了很多转换组合,但它从来没有让我将 M::foo 的地址转换为其他任何东西,所以我编写了一个运行时转换函数模板来强制它允许我将任何东西转换为任何其他类型我想毫无怨言(大家:请不要再喊“那不是便携的!”当然是便携的......这就是你使用它的方式,它可能是便携的,也可能不是便携的!):

    /*---- In a header file somewhere... ----*/
    #include <stdarg.h>
    template <typename ToTy, typename FromTy>
    ToTy forceCast_helper(int dummy, ...);
    
    template <typename ToTy, typename FromTy>
    inline ToTy forceCast(FromTy obj) {
        // ...has nothing to do with Star Wars!
        return forceCast_helper<ToTy,FromTy>(1, obj);
    }
    
    /*---- In a source file somewhere... ----*/
    template <typename ToTy, typename FromTy>
    ToTy forceCast_helper(int dummy, ...) {
        va_list args;
        va_start(args, dummy);
        ToTy ret = va_arg(args, ToTy);
        va_end(args);
        return ret;
    }
    

    这让我可以毫无错误地编译以下代码:

    typedef double(*doubleFuncDouble_t)(double);
    typedef double(A::*doubleClassAMemberfuncDouble_t)(double);
    
    f._function = forceCast<doubleFuncDouble_t>(&A::foo);
    
    // and then call this->foo(4.321) with it from within A::bar() as follows...
    
    (this->*(forceCast<doubleClassAMemberfuncDouble_t>(f._function)))(4.321);
    

    不幸的是,它在运行时出现了段错误。进一步调查表明,至少在 x86 的 32 位 Linux 的 GCC 上,sizeof(成员函数指针)为 8,而 sizeof(非成员函数指针)为 4。当我将 FuncR::_function 的类型更改为 uint64_t ,令人惊讶的是,调用成功了。 (我也很惊讶。)

    因此,无论您发现任何铸造魔法似乎都可以毫无错误地编译,实际上您根本无法将成员函数指针压缩到非成员函数指针中,至少在 GCC 上是 32-位 x86。即使可以,也不会像他的帖子中提到的“展开”那样封装“this”指针。


    不过我觉得还是有希望的。

    unwind 的帖子建议使用蹦床函数,但承认它需要单独传递“this”指针并在 C 代码中将其作为 void* 进行管理。我假设您的 C 库不可修改?如果是这样,假设您需要指定的此类函数指针数量有限,您应该仍然可以在不通过它传递“this”指针的情况下进行蹦床:

    您可以创建一个 A 类对象指针数组,大小与您将使用的这些 FuncR 函数指针对象的数量无关:

    A* arrayThatHoldsAObjPointers[8]; // assuming you only need 8 such FuncR func ptr objects
    

    然后创建许多物理静态非成员函数(每个都用与其关联数组索引对应的后缀编号方便地命名),并在每个函数的主体中,通过其关联的“A”对象调用 A::foo()在arrayThatHoldsAObjPointers中:

    double trampoline_0(double d) { return arrayThatHoldsAObjPointers[0]->foo(d); }
    double trampoline_1(double d) { return arrayThatHoldsAObjPointers[1]->foo(d); }
    ...and so on...
    

    然后,当您需要设置一个 FuncR 对象以供您的 C 库使用时,给它一个蹦床的地址,同时,将指向该 A 对象的指针存储在关联的 arrayThatHoldsAObjPointers[] 元素中。为了使设置这些的代码更易于使用,您还可以创建一个指向蹦床的函数指针数组。

    【讨论】:

    • 这样做似乎会使 A 类未定义,因为数组需要定义需要数组的 A...
    • @vanna - 您不需要定义 A 来定义指向 A 对象的指针数组。只需将“A”前向声明为一个类但不定义它(参见我的答案第一个代码块的第一行),数组的定义就会成功。但是,当然,要调用 arrayThatHoldsAObjPointers[0]-&gt;foo(d);,您必须在那时定义 A。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多