Menggunakan objek fungsi std :: fungsi umum dengan fungsi anggota dalam satu kelas

170

Untuk satu kelas saya ingin menyimpan beberapa pointer fungsi ke fungsi anggota dari kelas yang sama dalam satu objek mappenyimpanan std::function. Tapi saya gagal sejak awal dengan kode ini:

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

Saya menerima error C2064: term does not evaluate to a function taking 0 argumentsdi xxcallobjdikombinasikan dengan beberapa kesalahan template yang Instansiasi aneh. Saat ini saya bekerja pada Windows 8 dengan Visual Studio 2010/2011 dan pada Win 7 dengan VS10 gagal juga. Kesalahan harus didasarkan pada beberapa aturan C ++ aneh yang tidak saya ikuti

Christian Ivicevic
sumber

Jawaban:

301

Fungsi anggota non-statis harus dipanggil dengan objek. Artinya, selalu secara implisit melewati penunjuk "ini" sebagai argumennya.

Karena std::functiontanda tangan Anda menentukan bahwa fungsi Anda tidak mengambil argumen apa pun ( <void(void)>), Anda harus mengikat argumen pertama (dan satu-satunya).

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

Jika Anda ingin mengikat fungsi dengan parameter, Anda perlu menentukan placeholder:

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

Atau, jika kompiler Anda mendukung C ++ 11 lambdas:

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

(Saya tidak memiliki kompiler yang mampu mendukung C ++ 11 saat ini , jadi saya tidak dapat memeriksa yang ini.)

Alex B
sumber
1
Karena saya tidak bergantung pada boost, saya akan menggunakan ekspresi lambda;) Namun demikian terima kasih!
Christian Ivicevic
3
@AlexB: Boost.Bind tidak menggunakan ADL untuk placeholder, itu menempatkan mereka dalam namespace anonim.
ildjarn
46
Saya merekomendasikan untuk menghindari penangkapan global [=] dan menggunakan [ini] untuk membuatnya lebih jelas tentang apa yang ditangkap (Scott Meyers - Modern Efektif C ++ Bab 6. item 31 - Hindari mode pengambilan default)
Max Raskin
5
Cukup tambahkan sedikit tip: fungsi anggota pointer dapat secara implisit dilemparkan ke std::function, dengan tambahan thissebagai parameter pertama, sepertistd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung
@landerlyoung: Tambahkan nama fungsi katakanlah sebagai "f" di atas untuk memperbaiki sintaksis sampel. Jika Anda tidak memerlukan nama, Anda dapat menggunakan mem_fn (& Foo :: doSomethingArgs).
Val
80

Apa pun yang Anda butuhkan

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

sehingga Anda dapat memanggilnya pada contoh apa pun, atau Anda harus mengikat contoh tertentu, misalnya this

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
Armen Tsirunyan
sumber
Terima kasih atas jawaban yang bagus ini: D Tepat apa yang saya butuhkan, saya tidak dapat menemukan cara untuk mengkhususkan fungsi std :: untuk memanggil fungsi anggota pada instance kelas apa pun.
penelope
Ini mengkompilasi, tetapi apakah ini standar? Apakah Anda dijamin bahwa argumen pertama adalah this?
sudo rm -rf slash
@ sudorm-rfslash ya, Anda
Armen Tsirunyan
Terima kasih atas balasan @ArmenTsirunyan ... di mana dalam standar dapatkah saya mencari info ini?
sudo rm -rf slash
13

Jika Anda perlu menyimpan fungsi anggota tanpa instance kelas, Anda dapat melakukan sesuatu seperti ini:

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

Seperti apa bentuk penyimpanan tanpa otomatis ? Sesuatu seperti ini:

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

Anda juga dapat meneruskan penyimpanan fungsi ini ke penjilidan fungsi standar

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

Catatan masa lalu dan masa depan: Antarmuka yang lebih lama std :: mem_func ada, tetapi sejak itu sudah tidak digunakan lagi. Ada proposal, posting C ++ 17, untuk membuat pointer ke fungsi anggota dapat dipanggil . Ini akan sangat disambut.

Greg
sumber
@Danh std::mem_fnitu tidak dihapus; banyak kelebihan yang tidak perlu. Di sisi lain std::mem_funsudah ditinggalkan dengan C ++ 11 dan akan dihapus dengan C ++ 17.
Max Truxa
@Danh Itulah yang saya bicarakan;) Kelebihan "dasar" pertama masih ada:, template<class R, class T> unspecified mem_fn(R T::*);dan itu tidak akan hilang.
Max Truxa
@Danh Baca DR dengan seksama. 12 dari 13 kelebihan telah dihapus oleh DR. Yang terakhir itu tidak (dan tidak akan; baik di C ++ 11 atau C ++ 14).
Max Truxa
1
Mengapa memilih bawah? Setiap tanggapan lain mengatakan Anda harus mengikat instance kelas. Jika Anda membuat sistem yang mengikat untuk refleksi atau skrip, Anda tidak akan mau melakukannya. Metode alternatif ini valid dan relevan bagi sebagian orang.
Greg
Terima kasih Danh, saya telah mengedit beberapa komentar tentang antarmuka masa lalu dan masa depan yang terkait.
Greg
3

Sayangnya, C ++ tidak memungkinkan Anda untuk secara langsung mendapatkan objek yang dapat dipanggil yang merujuk ke objek dan salah satu fungsi anggotanya. &Foo::doSomethingmemberi Anda "pointer ke fungsi anggota" yang mengacu pada fungsi anggota tetapi bukan objek terkait.

Ada dua cara untuk mengatasi hal ini, satu digunakan std::binduntuk mengikat "fungsi pointer ke anggota" ke thispointer. Yang lainnya adalah menggunakan lambda yang menangkap thispointer dan memanggil fungsi anggota.

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

Saya lebih suka yang terakhir.

Dengan g ++ setidaknya mengikat fungsi anggota ke ini akan menghasilkan objek tiga-pointer dalam ukuran, menugaskan ini std::functionakan menghasilkan alokasi memori dinamis.

Di sisi lain, lambda yang menangkap thishanya berukuran satu pointer, menugaskannya ke tidak std::functionakan menghasilkan alokasi memori dinamis dengan g ++.

Walaupun saya belum memverifikasi ini dengan kompiler lain, saya menduga hasil yang sama akan ditemukan di sana.

plugwash
sumber
1

Anda dapat menggunakan functors jika Anda ingin kontrol yang kurang umum dan lebih tepat di bawah tenda. Contoh dengan api win32 saya untuk meneruskan pesan api dari suatu kelas ke kelas lain.

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

};

Prinsip penggunaan

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

Selamat mencoba dan terima kasih untuk semua yang telah berbagi pengetahuan


sumber
0

Anda dapat menghindari std::bindmelakukan ini:

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