2010-10-19 2 views
0

Я уже возился с указателями-членами по отношению к previous question. В приведенном ниже коде я вызываю методы класса (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; 
} 

(извините, меня снова, но этот член-указатели на функции делает меня Itch)

+0

Вы можете упростить этот код? –

+0

Я взял на себя смелость заменить ваш код гораздо более простым примером. Обязательно откат, если вам это не нравится ... –

+0

Это не работает. Кажется, это работает. Там огромная разница. – jalf

ответ

1

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

Упорное вещь, которую вы манипулируете, скорее всего, стоят в INT в байт в случае отображения А (потому что, если объект был действительно B, то это смещение, где count элемент будет расположен.

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

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

+0

Итак, сделайте это и получите тот же тип результатов, что и вы, - ваше объяснение имеет смысл. Следующий вопрос: где он находит код, который выполняется? – slashmais

+0

На уровне машины это всего лишь байты и смещения. Машина в основном интерпретирует вызов 'B :: multiply' как" приращение целого числа со смещением 0 (поскольку 'count' является первым членом) из указателя' this', а затем умножает его на значения аргументов функции и возвращает результат". За исключением того, что у вас нет экземпляра B, и используемые байты не являются членами 'count' экземпляра B. – visitor

8

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

Итак, компилятор закрывается.

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

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

Или, например, заставляет носовых демонов вылететь из вашего носа.

Cheers & hth.

+0

+1 для носовых демонов –

+0

LOL, да, мне интересно, почему использование этого указателя в A успешно вызывает участников в B. 'T'is, проходящих странно ... – slashmais

+0

Указатели функций должны быть «дикими», но они «Вызов правильного кода. Ваш ответ не объясняет это. – slashmais

1

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

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

1

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

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

Когда вы позже попытаетесь позвонить, например, B::multiply, эта функция не знает что он не работает над объектом класса B, поэтому он с радостью скроет байты aA, которые соответствовали бы члену B::count, если бы это был объект B. Скорее всего, эти байты фактически используются A::funcs, но, по-видимому, не для чего-то критического. Если изменить класс А, чтобы:

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-таблицу объекта, на который вызывается функция.