C++ преобразование указателя функции в уникальный" хэш " ключ


Se оригинальный вопрос внизу.

Я думаю, что понимаю, что вы, ребята, сейчас говорите – что, поскольку внутренняя структура указателя функции-члена является компилятором/машиной, это действительно невозможно сделать, что я пытаюсь. Поэтому, даже если он работает, когда я его тестирую – у меня нет гарантии, что он будет работать на других компиляторах/машинах.

Есть ли другой способ сделать то, что я хочу тогда?

У меня есть класс шаблона и базовый класс шаблона для этого класса, и у меня есть класс делегата, который содержит std:: карту всех событий, которые класс делегата должен вызывать, когда он вызывается.

Причина, по которой мне нужна карта, заключается как в том, чтобы гарантировать, что одна и та же функция-член (событие, указывающее на функцию-член) не добавляется больше одного раза, так и в том, чтобы сделать возможным удаление событий из карты с помощью объекта и функции-члена, первоначально использовавшихся при создании экземпляра объекта-события.

template <class T_arg1> struct EventBase1
{
public:
    bool operator < (const EventBase1 &event1) const { return _key < event1._key; };
    virtual void operator()(T_arg1 t1) const {};
    std::pair<intptr_t, intptr_t> _key;
};

template <class T, class T_arg1> struct Event1: public EventBase1<T_arg1>
{
    template <class T_arg1> friend class Delegate1;
    typedef typename void (T::*Method)(T_arg1);
private:
    Method _method;
    T* _object;
public:
    Event1(T* object, Method method): _object(object), _method(method)
    {
        _key = std::pair<intptr_t, intptr_t>(reinterpret_cast<intptr_t>(object), reinterpret_cast<intptr_t>( reinterpret_cast<void*&>(method)));
    };
    virtual void operator()(T_arg1 t1) const {
        (_object->*_method)(t1);
    };
};

template <class T_arg1> class Delegate1
{
public:
    typedef typename EventBase1<T_arg1> T_event;
    void operator += (T_event* observer)
    {
        assert(observer);
        _observers[*observer] = observer;
    };
    void operator -= (const T_event &observer)
    {
        std::map<T_event, T_event*>::iterator i = _observers.find(observer);
        if(i != _observers.end()) {
            delete i->second;
            _observers.erase(i);
        }
    };
    void operator()(T_arg1 t1)
    {
        for(std::map<T_event, T_event*>::iterator i = _observers.begin(); i != _observers.end(); i++) {
            (*(i->second))(t1);
        }
    };
private:
    std::map<T_event, T_event*> _observers;     
};

Первоначальный вопрос:

Я сохраняю функцию указатели в std::map, и я генерирую свой ключ для карты следующим образом: std::pair<int, int>( (int)((int*)object), (int)(static_cast<const void*>(&method)) ).

method является указателем функции (метода), а object - указателем на объект метода.

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

Я никогда полностью не понимал указатели функций, но я предполагаю, что я получаю адрес указателя, а не адрес функции, и компилятор не позволит мне сделать это. это ((int)(static_cast<const void*>(method))).

Итак, мой вопрос - как я могу получить уникальный ключ от указателя функции, который будет таким же, если я позже получу ключ от другого указателя функции, указывающего тот же метод?

Заранее спасибо, Мартин

2 2

2 ответа:

Второе не является законным: формально, вы не можете преобразовать указатель в функция для указателя на данные (а void* - указатель на данные). Кроме того, вы не гарантируете, что сможете преобразовать любой указатель в int; преобразование является законным только в том случае, если int по крайней мере так же велико, как указатель (что означает, что ваш код не сможет скомпилироваться на большинстве 64 битных систем).

Существует несколько способов решения этой. Во-первых, на большинстве (всех?) современный машины, указатели на функции и указатели на данные делать имеют одинаковые размер и представление. (На самом деле Posix этого требует. Даже если это это было не так на первых машинах Unix, которые я использовал.) Если мы предположим, что это, вы можете гарантировать достаточно большой интегральный тип, используя intptr_t, и "трюк" компилятора с использованием дополнительного уровня косвенности:

std::pair<intptr_t, intptr_t>(
    reinterpret_cast<intptr_t>( reinterpret_cast<void*&>( object ) ),
    reinterpret_cast<intptr_t>( reinterpret_cast<void*&>( method ) ) )

(это предполагает, что object и method являются вашими указателями на объект и функция.)

Обратите внимание, что это не работает для указателей на функции-члены. Указатель к функциям-членам относятся совершенно разные звери, а я нет думайте, что есть какой-то эффективный способ использовать их в качестве ключа таким образом (поскольку они могут содержать и часто содержат заполняющие или неназначенные поля, в некоторые случаи).

Если на то пошло, формально это не гарантируется даже для нормальных людей. указатели. Стандарт допускает указатели, чтобы не волнует, биты, или несколько различных представлений указателей для сравнения равны. В практика, однако, безопасна на большинстве (всех?) современный машины.

Вы должны хорошо писать:

reinterpret_cast<uintptr_t>(method)

[edit] - для указателей на методы вам придется остаться с приведениями в стиле c, как объясняется в этом так: reinterpret_cast to void * не работает с указателями функций

&метод, как вы подозреваете указатель на указатель, так что это не то, что вы хотите

Uintptr_t лучше, чем int, потому что он гарантированно будет иметь тот же размер, что и указатель