Указатели на функции-члены и фантомные классы - PullRequest
0 голосов
/ 19 октября 2010

Я возился с указателями на функции-члены относительно предыдущего вопроса . В приведенном ниже коде я вызываю методы для класса (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 ]

8 голосов
/ 19 октября 2010

Ваше приведение в макросе HANDLER говорит компилятору: «Заткнись! Я знаю, что делаю!».

Итак, компилятор отключается.

У вас все еще есть неопределенное поведение, но одно свойство UB - то, что в некоторых случаях он делает то, что вы наивно ожидаете, или то, что вы хотите, чтобы он делал.

Но не удивляйтесь, если такой код вылетает, вызывает сбои или таинственный неверный результат в явно не связанном коде.

Или, например, из-за носовых демонов вылетает нос.

Приветствия и hth.

1 голос
/ 19 октября 2010

Ваш код вызывает неопределенное поведение , пытаясь вызвать член класса B, используя объект класса A. Мы можем попытаться объяснить, как компилятор может прийти к поведению, которое вы наблюдали, но тамНе гарантируется, что вы получите такое же поведение, если вы что-то измените (добавите / удалите элемент, измените настройки компилятора или используйте другой компилятор).

С приведением в макросе HANDLER вы говоритекомпилятор не предупреждает вас об использовании несовместимых типов, а просто делает так, как вы говорите.В этом случае вы указываете компилятору переинтерпретировать адрес члена любого класса как адрес члена класса A.

Когда вы позже попытаетесь вызвать, например, B::multiply,Функция не знает, что она не работает с объектом класса B, поэтому она с радостью ударит байты aA, которые будут соответствовать элементу B::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-таблицу в качестве указателя на эту функцию (вместе с каким-то указанием на то, что это смещение v-таблицы).Затем код может определить на месте вызова, может ли он выполнить прямой вызов функции-члена или нужно ли выполнить косвенный вызов через v-таблицу объекта, для которого вызывается функция.

1 голос
/ 19 октября 2010

Результатом является просто неопределенное поведение. Например, я получаю, что b = 2083899728 и d = -552766888.

Постоянная вещь, которой вы манипулируете, - это, скорее всего, байты int в экземпляре карты A (потому что, если объект действительно был B, то это смещение, в котором будет расположен элемент count.

В моей реализации stdlib первым элементом map является функция сравнения, в данном случае экземпляр std::less<int>. Его размер равен 1, но после этого должны быть неиспользованные байты заполнения для выравнивания других элементов карты. То есть (по крайней мере) первые четыре байта этого экземпляра std::map содержат только мусор, который ни для чего не используется (std :: less не имеет членов данных и не хранит состояние, он просто занимает место в карта). Это объясняет, почему код не дает сбоя - он изменяет часть экземпляра карты, которая не влияет на функционирование карты.

Добавьте больше элементов данных в B до count, и теперь count++ повлияет на важные части внутреннего представления карты, и вы можете получить сбой.

1 голос
/ 19 октября 2010

C-casting позволяет вам избежать всевозможных ужасных действий, но не означает, что это нормально, просто не делайте этого.

Полностью избавьтесь от своего макроса и не применяйте каст.Вероятно, вы можете использовать boost :: function и boost :: bind, чтобы получить поведение, которое вам действительно нужно.

...