0

我一直在搞乱与先前问题相关的成员函数指针。在下面的代码中,我调用类 (B) 上的方法来更改其中的变量(计数),但我从未创建此类的实例。为什么这行得通?

#include <iostream>
#include <string>
#include <map>

class A;
typedef int (A::*MEMFUNC)(int, int);

#define HANDLER(aclass, aproc) (MEMFUNC)(&aclass::aproc)

enum
{
    ADD=1,
    MUL,
    SUB,
    DIV
};

class B
{
    int count;
public:
    B() : count(0) {}
    ~B() {}
    int multiply(int x, int y) { count++; return x*y*count; }
    int divide(int x, int y) { count++; if (y!=0) return (x/y)*count; else return 0; }
};

class A
{
    std::map< int, MEMFUNC > funcs;
public:
    A() { AddLocals(); }
    ~A() {}
    int CallLocal(int nID, int x, int y)
    {
        MEMFUNC f = funcs[nID];
        if (f) return (this->*f)(x, y);
        else return 0;
    }
    void AddLocals()
    {
        Add(ADD, HANDLER(A, plus));
        Add(MUL, HANDLER(B, multiply));
        Add(SUB, HANDLER(A, subtract));
        Add(DIV, HANDLER(B, divide));
    }
    void Add(int nID, MEMFUNC f) { funcs[nID] = f; }
    int plus(int x, int y) { return x+y; }
    int subtract(int x, int y) { return x-y; }

};

int main()
{
    A aA;
    int a,b,c,d;

    a = aA.CallLocal(ADD,8,2);
    b = aA.CallLocal(MUL,8,2);
    c = aA.CallLocal(SUB,8,2);
    d = aA.CallLocal(DIV,8,2);

    std::cout << "a = " << a << "\n" 
              << "b = " << b << "\n" 
              << "c = " << c << "\n" 
              << "d = " << d << "\n";


    return 0;
}

(对不起,我又来了,但是这个成员函数指针让我很痒)

4

4 回答 4

8

你在HANDLER宏 def 中的转换告诉编译器“闭嘴!我知道我在做什么!”。

所以编译器关闭了。

您仍然有未定义的行为,但 UB 的一个属性是,在某些情况下,它会执行您天真的期望或您希望它执行的操作。

但是,如果这样的代码崩溃,或者导致崩溃或神秘的错误导致明显完全不相关的代码,请不要感到惊讶。

或者,例如,导致鼻恶魔飞出你的鼻子。

干杯&hth。

于 2010-10-19T11:10:58.307 回答
1

C-casting 可以让你摆脱各种可怕的行为,但并不意味着可以这样做,所以干脆不要。

完全摆脱你的宏,不要施放。您可能可以使用 boost::function 和 boost::bind 来获得您真正想要的行为。

于 2010-10-19T11:33:14.210 回答
1

结果只是未定义的行为。例如,我明白了b = 2083899728并且d = -552766888.

您正在操作的持久性事物很可能是 A 的映射实例中的 int 值字节(因为如果对象确实是 B,那么这就是count成员所在的偏移量。

在我的 stdlib 实现中,map 的第一个成员是比较函数,在本例中是std::less<int>. 它的大小为 1,但之后必须有未使用的填充字节来对齐 map 的其他成员。也就是说,(至少)这个实例化的前四个字节std::map只包含不用于任何东西的垃圾(std::less 没有数据成员并且不存储状态,它只是在映射中占用空间) . 这可以解释为什么代码不会崩溃 - 它正在修改不影响地图功能的地图实例的一部分。

之前在 B 中添加更多数据成员count,现在count++会影响地图内部表示的关键部分,您可能会崩溃。

于 2010-10-19T12:06:32.367 回答
1

您的代码通过尝试使用 A 类的对象调用 B 类的成员来调用未定义的行为。我们可以尝试解释编译器如何得出您观察到的行为,但不能保证您会得到相同的结果如果您更改任何内容(添加/删除成员、更改编译器设置或使用不同的编译器)时的行为。

使用HANDLER宏中的转换,您告诉编译器不要警告您使用不兼容的类型,而只是按照您的指示去做。在这种情况下,您告诉编译器将任何类成员的地址重新解释为类 A 成员的地址。

例如,当您稍后尝试调用时B::multiply,该函数不知道它不是在处理 B 类的对象,因此如果它是一个对象,它会很高兴地破坏与aAB::count成员相对应的字节B。最有可能的是,这些字节实际上正在被 使用A::funcs,但显然不是用于任何关键的。如果您将 A 类更改为:

class A
{
    int count;
    std::map< int, MEMFUNC > funcs;
public:
    A() : count(0) { AddLocals(); }
    ~A() {}
    int CallLocal(int nID, int x, int y)
    {
        MEMFUNC f = funcs[nID];
        if (f) return (this->*f)(x, y);
        else return 0;
    }
    int Count()
    {
        return count;
    }
    void AddLocals()
    {
        Add(ADD, HANDLER(A, plus));
        Add(MUL, HANDLER(B, multiply));
        Add(SUB, HANDLER(A, subtract));
        Add(DIV, HANDLER(B, divide));
    }
    void Add(int nID, MEMFUNC f) { funcs[nID] = f; }
    int plus(int x, int y) { return x+y; }
    int subtract(int x, int y) { return x-y; }

};

然后在各个地方打印结果aA.Count()可能会显示效果。

编译器正在调用预期的函数,因为它们是非虚拟成员函数。
非成员函数和非虚拟成员函数之间的唯一区别在于隐藏的参数,它this在成员函数中提供指针。因此,如果您获取非虚拟成员函数的地址,您将获得一个对于每个函数都不同的固定地址。
如果成员函数是虚拟的,那么编译器很可能会将索引作为该函数的指针返回到 v-table 中(连同某种指示它是 v-table 偏移量的指示)。然后代码可以在调用点确定是否可以直接调用成员函数,或者是否需要通过调用函数的对象的 v-table 进行间接调用。

于 2010-10-19T12:47:06.653 回答