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

170

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

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 ++, которым я не следую

Кристиан Ивицевич
источник

Ответы:

301

Нестатическая функция-член должна вызываться с объектом. То есть он всегда неявно передает указатель «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 , способный компилятор под рукой прямо сейчас , поэтому я не могу проверить это.)

Алекс Б
источник
1
Поскольку я не зависим от наддува, я буду использовать лямбда-выражения;) Тем не менее, спасибо!
Кристиан Ивичевич
3
@AlexB: Boost.Bind не использует ADL для заполнителей, он помещает их в анонимное пространство имен.
ildjarn
46
Я рекомендую избегать глобального захвата [=] и использовать [это], чтобы сделать его более понятным (Скотт Мейерс - Эффективная современная C ++ Глава 6. Пункт 31 - Избегайте режимов захвата по умолчанию)
Макс Раскин
5
Просто добавьте небольшой совет: указатель на функцию-член может быть неявно приведен std::function, с дополнительным в thisкачестве первого параметра, напримерstd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung
@landerlyoung: добавьте имя функции, скажем, как «f» выше, чтобы исправить пример синтаксиса. Если вам не нужно имя, вы можете использовать mem_fn (& Foo :: doSomethingArgs).
Вал
80

Либо вам нужно

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

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

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

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

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);

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

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

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

К сожалению, 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 ++, по крайней мере, привязка функции-члена к этому приведет к объекту с тремя указателями в размере, а присвоение этому std::functionприведет к динамическому распределению памяти.

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

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

plugwash
источник
1

Вы можете использовать функторы, если вы хотите менее общий и более точный контроль под капотом. Пример с моим 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);
    }
};

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


источник
0

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

std::function<void(void)> f = [this]-> {Foo::doSomething();}
aggsol
источник