Використання загальних об’єктів std :: function з функціями-членами в одному класі


169

Для одного класу я хочу зберегти деякі покажчики функцій на функції-членів одного класу в одному об'єкті, що 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

Нестатичну функцію члена потрібно викликати об'єктом. Тобто він завжди неявно передає «цей» покажчик як свій аргумент.

Оскільки ваш 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
Оскільки я не залежний від boost, я буду використовувати лямбда-вирази;) Тим не менше дякую!
Крістіан Івічевич

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 :: для виклику функції-члена в будь-якому екземплярі класу.
пенелопа

Це компілюється, але це стандартно? Ви гарантовані, що перший аргумент this?
sudo rm -rf slash

@ sudorm-rfslash так, ти
Армен Цирунян

Дякую за відповідь @ArmenTsirunyan ... де в стандарті я можу шукати цю інформацію?
sudo rm -rf slash

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, щоб зробити вказівник на функції учасника можливим . Це було б найкраще.


@Danh неstd::mem_fn було видалено; купа непотрібних перевантажень була. З іншого боку, це було знято з C ++ 11 і буде видалено C ++ 17. std::mem_fun
Макс Трукса

@Danh Саме про це я і говорю;) Найперша "основна" перевантаження все ще є: template<class R, class T> unspecified mem_fn(R T::*);і вона не піде.
Макс Трукса

@Danh Уважно прочитайте ДР . ЗР було усунено 12 з 13 перевантажень. Останнього не було (і не буде; ні в C ++ 11, ні в C ++ 14).
Макс Трукса

1
Чому голосування проти? Кожна інша відповідь сказала, що ви повинні зв’язати екземпляр класу. Якщо ви створюєте систему зв’язування для рефлексії чи сценаріїв, ви не хочете цього робити. Цей альтернативний метод є дійсним і актуальним для деяких людей.
Грег

Дякую Дане, я відредагував кілька коментарів щодо пов'язаних інтерфейсів минулого та майбутнього.
Грег

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

З іншого боку, лямбда, який захоплює, thisмає лише один покажчик за розміром, присвоєння його std::functionволі не призведе до динамічного розподілу пам'яті за допомогою g ++.

Поки я не підтвердив це з іншими компіляторами, я підозрюю, що подібні результати знайдуться там.


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

Слухач.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;
    }
};

Диспетчер.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();}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.