Как хэшировать и сравнивать указатель на функцию-член?

Как я могу хэшировать (std::tr1::hash или boost::hash) функцию указателя на член С++?

Пример:

У меня есть несколько логических (Class::*functionPointer)() (не статических), которые указывают на несколько разных методов класса Class, и мне нужно хешировать эти указатели на функцию-член.

Как я могу это сделать?

Кроме того, как я могу сравнить (std::less) эти указатели функций-членов, чтобы я мог сохранить их в std::set?


person AllDayCpp    schedule 25.08.2009    source источник
comment
Обычно нет никаких причин для хеширования указателя, поскольку он указывает непосредственно на то, к чему вы хотите получить доступ. Пожалуйста, предоставьте код, иллюстрирующий то, о чем вы спрашиваете.   -  person    schedule 25.08.2009
comment
Когда бы вы сказали, что один указатель функции «меньше», чем другой?   -  person Bojan Resnik    schedule 25.08.2009
comment
@bojan: Если единственная цель сравнения — сохранить их в отсортированном списке, подойдет любой детерминированный порядок. Например, двоичное значение.   -  person erikkallen    schedule 25.08.2009
comment
У меня есть класс с указателем на функцию-член в качестве переменной-члена. Мне нужно сохранить этот класс в std::set и в std::hash_set, поэтому ему нужен хэш и std::less для этого указателя на функцию-член.   -  person AllDayCpp    schedule 25.08.2009
comment
Будут ли экземпляры вашего класса идентичны во всех полях, кроме указателя на функцию-член? Если нет, то вам не нужно включать его в хэш/сравнение, что позволяет избежать проблемы.   -  person Steve Jessop    schedule 25.08.2009


Ответы (3)


Все объекты C++, включая указатели на функции-члены, представлены в памяти в виде массива символов. Итак, вы можете попробовать:

bool (Class::*fn_ptr)() = &Class::whatever;
const char *ptrptr = static_cast<const char*>(static_cast<const void*>(&fn_ptr));

Теперь обработайте ptrptr как указатель на массив из (sizeof(bool (Class::*)())) байтов и хешируйте или сравните эти байты. Вы можете использовать unsigned char вместо char, если хотите.

Это гарантирует отсутствие ложных срабатываний — в C++03 указатели на функции-члены являются POD, что означает, среди прочего, что их можно копировать с помощью memcpy. Это означает, что если они имеют одинаковые побайтовые значения, то они одинаковы.

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

Это потенциально сложно: указатели функций-членов неудобны, и хранилище, вероятно, будет включать разное количество неучаствующего «неиспользуемого пространства» в зависимости от того, на какую функцию указывает (виртуальная, унаследованная). Таким образом, вам, вероятно, придется довольно активно взаимодействовать с деталями реализации вашего компилятора. Эта статья может помочь вам начать работу: http://www.codeproject.com/KB/cpp/FastDelegate.aspx

Более чистой альтернативой может быть линейный поиск в массиве, чтобы «канонизировать» все ваши указатели на функции, а затем сравнивать и хешировать на основе положения «канонического» экземпляра этого указателя функции в вашем массиве. Зависит от ваших требований к производительности. И даже если есть требования, неужели класс (и его производные классы) имеет так много функций, что линейный поиск займет так много времени?

typedef bool (Class::*func)();
vector<func> canon;

size_t getIndexOf(func fn_ptr) {
    vector<func>::iterator it = find(canon.begin(), canon.end(), fn_ptr);
    if (it != canon.end()) return it - canon.begin();
    canon.push_back(func);
    return canon.size() - 1;
}
person Steve Jessop    schedule 25.08.2009
comment
Спасибо, char * делает свое дело! Только в моем компиляторе мне нужен reinterpret_cast вместо static_cast. - person AllDayCpp; 25.08.2009
comment
Отличное решение некоторых острых вопросов, +1. Мне не приходило в голову, что pmf1 == pmf2 не обязательно подразумевает побитовую идентичность. - person j_random_hacker; 25.08.2009
comment
Указатель на функцию-член может содержать заполнение, которое будет игнорироваться при сравнении на равенство, и может принимать случайные значения. Хеширование любых байтов заполнения приведет к сбою хеш-функции. - person James Kanze; 25.10.2012
comment
@James: я обсуждаю это, начиная с Проблема в том, что представление хранилища указателей на функции-члены может включать биты, которые не участвуют в значении - person Steve Jessop; 25.10.2012
comment
Хотя идея с каноизированным индексом, безусловно, хороша (спасибо за эту идею!), я хотел бы добавить ПРЕДУПРЕЖДЕНИЕ: насколько мне известно, невозможно сравнивать на равенство указатели членов на виртуальные функции (например, функции в интерфейс / азбука). Поскольку одно и то же смещение может фактически разрешаться для разных реализаций, в зависимости от того, какой фактический экземпляр вы привязываете к указателю члена. Таким образом, этот подход ломается в этом случае. - person Ichthyo; 02.01.2015
comment
@Steve, ваша реализация (компилятор) может быть небрежной и разрешать такие сравнения, но стандарт является явным. См. §5.10. Если любой из них является указателем на виртуальную функцию-член, результат не указан. - person Ichthyo; 04.01.2015
comment
см. черновик C++14 Страница 120f (находится внизу страницы 134 в связанном PDF-файле) - person Ichthyo; 04.01.2015

Я не мог указать указатель (в компиляторе Microsoft 2010), как описано в предыдущем ответе, но это работает для меня:

static string fmptostr(int atype::*opt)
  {
      char buf[sizeof(opt)];
      memcpy(&buf,&opt,sizeof(opt));
      return string(buf,sizeof(opt));
  }

Что касается побитовой идентичности указателя, он может быть побитовым, так что кажется, если используются соответствующие переключатели компилятора. По крайней мере, это верно для компилятора Microsoft, например, с использованием #pragma pointers_to_members и переключателя.../vmg

person Aftershock    schedule 08.09.2011

Если ваш указатель функции-члена уникален, что верно в большинстве случаев для подписок на основе обратного вызова, вы можете использовать галочку с type_index, уникальность которого гарантируется уникальностью типа (т.е. Class::Method) в вашей программе, и его можно хранить в unordered_map, т.е.

struct MyEvent {

    using fn_t = std::function<void(MyEvent &)>;
    using map_t = std::unordered_map<std::type_index, fn_t>;


    template <typename Handler>
    void subscribe(Object& obj, Handler&& handler) {
        fn_t fn = [&, handler = std::move(handler)](MyEvent& event) {
            (obj.*handler)(event);
        }
        std::type_index index = typeid(Handler);
        subscribers.emplace(std::move(index), std::move(fn));
    }

    void fire() {
        for(auto& pair: subscribers) {
            auto& fn = pair.second;
            fn(*this);
        }
    }

    map_t subscribers;
}

И пример подписки и события пожара:

MyEvent event;
MyObject obj = ...;
event.subscribe(obj, &MyObject::on_event );
...
event.fire();

Итак, приведенный выше пример дает вам уникальность класса/метода, и если вам нужна уникальность объекта/метода, тогда у вас должна быть структура, которая предоставляет комбинированный хеш, предполагая, что есть std::hash<MyObject> и уже есть std::hash<std::type_index> для указателя на функцию-член.

person Ivan Baidakou    schedule 22.05.2019