Использование общих объектов std :: function с функциями-членами в одном классе

Для одного класса я хочу сохранить несколько указателей функций на функции-члены того же класса в одном map, хранящем std::function объектах. Но в самом начале я терплю неудачу с этим кодом:

#include <functional>

class Foo {
    public:
        void doSomething() {}
        void bindFunction() {
            // ERROR
            std::function<void(void)> f = &Foo::doSomething;
        }
};

Я получаю error C2064: term does not evaluate to a function taking 0 arguments в xxcallobj в сочетании с некоторыми странными ошибками создания экземпляров шаблона. В настоящее время я работаю над Windows 8 с Visual Studio 2010/2011, а над Win 7 с VS10 тоже не работает. Ошибка должна быть основана на каких-то странных правилах C ++, которым я не следую.


person Christian Ivicevic    schedule 28.09.2011    source источник


Ответы (6)


Нестатическая функция-член должна вызываться с объектом. То есть он всегда неявно передает указатель this в качестве аргумента.

Поскольку ваша подпись std::function указывает, что ваша функция не принимает никаких аргументов (<void(void)>), вы должны привязать первый (и единственный) аргумент.

std::function<void(void)> f = std::bind(&Foo::doSomething, this);

Если вы хотите связать функцию с параметрами, вам нужно указать заполнители:

using namespace std::placeholders;
std::function<void(int,int)> f = std::bind(&Foo::doSomethingArgs, this, std::placeholders::_1, std::placeholders::_2);

Или, если ваш компилятор поддерживает лямбда-выражения C ++ 11:

std::function<void(int,int)> f = [=](int a, int b) {
    this->doSomethingArgs(a, b);
}

(У меня нет под рукой компилятора с поддержкой C ++ 11 прямо сейчас, поэтому я не могу проверить этот.)

person Alex B    schedule 28.09.2011
comment
Поскольку я не зависим от ускорения, я буду использовать лямбда-выражения;) Тем не менее, спасибо! - person Christian Ivicevic; 28.09.2011
comment
@AlexB: Boost.Bind не использует ADL для заполнителей, он помещает их в анонимное пространство имен. - person ildjarn; 28.09.2011
comment
Я рекомендую избегать глобального захвата [=] и использовать [this], чтобы прояснить, что захватывается (Скотт Мейерс - Эффективный современный C ++ Глава 6. Пункт 31 - Избегайте режимов захвата по умолчанию) - person Max Raskin; 29.08.2016
comment
Просто добавьте небольшой совет: указатель функции-члена может быть неявно приведен к std::function с дополнительным this в качестве первого параметра, например std::function<void(Foo*, int, int)> = &Foo::doSomethingArgs - person landerlyoung; 25.11.2019
comment
@landerlyoung: добавьте имя функции, скажем, как f выше, чтобы исправить пример синтаксиса. Если вам не нужно имя, вы можете использовать mem_fn (& Foo :: doSomethingArgs). - person Val; 23.05.2020

Либо тебе нужно

std::function<void(Foo*)> f = &Foo::doSomething;

так что вы можете вызвать его в любом экземпляре, или вам нужно привязать конкретный экземпляр, например this

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
person Armen Tsirunyan    schedule 28.09.2011
comment
Спасибо за этот отличный ответ: D Именно то, что мне нужно, я не мог найти, как специализировать std :: function для вызова функции-члена для любого экземпляра класса. - person penelope; 05.04.2018
comment
Это компилируется, но стандартно ли это? Вам гарантировано, что первым аргументом будет this? - person sudo rm -rf slash; 09.07.2019
comment
@ sudorm-rfslash да, ты - person Armen Tsirunyan; 09.07.2019
comment
Спасибо за ответ @ArmenTsirunyan ... где в стандарте эту инфу искать? - person sudo rm -rf slash; 09.07.2019

Если вам нужно сохранить функцию-член без экземпляра класса, вы можете сделать что-то вроде этого:

class MyClass
{
public:
    void MemberFunc(int value)
    {
      //do something
    }
};

// Store member function binding
auto callable = std::mem_fn(&MyClass::MemberFunc);

// Call with late supplied 'this'
MyClass myInst;
callable(&myInst, 123);

Как бы выглядел тип хранилища без auto? Что-то вроде этого:

std::_Mem_fn_wrap<void,void (__cdecl TestA::*)(int),TestA,int> callable

Вы также можете передать это хранилище функций в стандартную привязку функции

std::function<void(int)> binding = std::bind(callable, &testA, std::placeholders::_1);
binding(123); // Call

Прошлые и будущие примечания: существовал более старый интерфейс std :: mem_func, но с тех пор он устарел. Существует предложение, опубликованное на C ++ 17, сделать указатель на вызываемые функции-члены. Это было бы очень приятно.

person Greg    schedule 03.11.2016
comment
@Danh std::mem_fn не удален; куча лишних перегрузок была. С другой стороны, std::mem_fun устарел в C ++ 11 и будет удален в C ++ 17. - person Max Truxa; 03.11.2016
comment
@Danh Это именно то, о чем я говорю;) Самая первая базовая перегрузка все еще существует: template<class R, class T> unspecified mem_fn(R T::*);, и она никуда не денется. - person Max Truxa; 03.11.2016
comment
@Danh Внимательно прочтите DR. 12 из 13 перегрузок были устранены ДР. Этого последнего не было (и не будет; ни в C ++ 11, ни в C ++ 14). - person Max Truxa; 03.11.2016
comment
Почему голосование против? Во всех остальных ответах говорилось, что вы должны привязать экземпляр класса. Если вы создаете систему привязки для отражения или написания сценариев, вам не захочется этого делать. Этот альтернативный метод действителен и актуален для некоторых людей. - person Greg; 04.11.2016
comment
Спасибо, Дан, я отредактировал несколько комментариев о связанных прошлых и будущих интерфейсах. - person Greg; 07.11.2016

К сожалению, C ++ не позволяет напрямую получить вызываемый объект, ссылающийся на объект и одну из его функций-членов. &Foo::doSomething дает вам «указатель на функцию-член», который относится к функции-члену, но не к связанному объекту.

Есть два способа обойти это: один - использовать std::bind для привязки «указателя на функцию-член» к указателю this. Другой - использовать лямбда, которая захватывает указатель this и вызывает функцию-член.

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
std::function<void(void)> g = [this](){doSomething();};

Я бы предпочел последнее.

С g ++, по крайней мере, привязка функции-члена к this приведет к получению объекта размером с тремя указателями, присвоение этого std::function приведет к динамическому распределению памяти.

С другой стороны, лямбда, которая захватывает this, имеет размер только один указатель, присвоение его std::function не приведет к динамическому распределению памяти с помощью g ++.

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

person plugwash    schedule 18.11.2019

Вы можете избежать std::bind этого:

std::function<void(void)> f = [this]-> {Foo::doSomething();}
person aggsol    schedule 28.05.2020

Вы можете использовать функторы, если вам нужен менее общий и более точный элемент управления. Пример с моим win32 api для пересылки сообщения api из класса в другой класс.

IListener.h

#include <windows.h>
class IListener { 
    public:
    virtual ~IListener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
};

Listener.h

#include "IListener.h"
template <typename D> class Listener : public IListener {
    public:
    typedef LRESULT (D::*WMFuncPtr)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

    private:
    D* _instance;
    WMFuncPtr _wmFuncPtr; 

    public:
    virtual ~Listener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) override {
        return (_instance->*_wmFuncPtr)(hWnd, uMsg, wParam, lParam);
    }

    Listener(D* instance, WMFuncPtr wmFuncPtr) {
        _instance = instance;
        _wmFuncPtr = wmFuncPtr;
    }
};

Dispatcher.h

#include <map>
#include "Listener.h"

class Dispatcher {
    private:
        //Storage map for message/pointers
        std::map<UINT /*WM_MESSAGE*/, IListener*> _listeners; 

    public:
        virtual ~Dispatcher() { //clear the map }

        //Return a previously registered callable funtion pointer for uMsg.
        IListener* get(UINT uMsg) {
            typename std::map<UINT, IListener*>::iterator itEvt;
            if((itEvt = _listeners.find(uMsg)) == _listeners.end()) {
                return NULL;
            }
            return itEvt->second;
        }

        //Set a member function to receive message. 
        //Example Button->add<MyClass>(WM_COMMAND, this, &MyClass::myfunc);
        template <typename D> void add(UINT uMsg, D* instance, typename Listener<D>::WMFuncPtr wmFuncPtr) {
            _listeners[uMsg] = new Listener<D>(instance, wmFuncPtr);
        }

};

Принципы использования

class Button {
    public:
    Dispatcher _dispatcher;
    //button window forward all received message to a listener
    LRESULT onMessage(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        //to return a precise message like WM_CREATE, you have just
        //search it in the map.
        return _dispatcher[uMsg](hWnd, uMsg, w, l);
    }
};

class Myclass {
    Button _button;
    //the listener for Button messages
    LRESULT button_listener(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        return 0;
    }

    //Register the listener for Button messages
    void initialize() {
        //now all message received from button are forwarded to button_listener function 
       _button._dispatcher.add(WM_CREATE, this, &Myclass::button_listener);
    }
};

Удачи и спасибо всем за то, что поделились знаниями.

person Community    schedule 06.03.2019