Bir sınıfta üye işlevlere sahip genel std :: işlev nesnelerini kullanma


171

Bir sınıf için ben bir de aynı sınıfın eleman fonksiyonları bazı fonksiyon işaretçileri depolamak istediğiniz mapdepolama std::functionnesneler. Ama ben başlangıçta bu kod ile başarısız:

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

Aldığım error C2064: term does not evaluate to a function taking 0 argumentsiçinde xxcallobjbazı garip şablon örneği hataları ile birleştirdi. Şu anda Visual Studio 2010/2011 ile Windows 8 üzerinde çalışıyorum ve VS10 ile Win 7 üzerinde de başarısız oluyor. Hata takip etmediğim bazı garip C ++ kurallarına dayanmalıdır

Yanıtlar:


301

Bir nesneyle statik olmayan bir üye işlevi çağrılmalıdır. Yani, her zaman örtük olarak "bu" işaretçiyi argümanı olarak geçirir.

Senin Çünkü std::functionimza belirtir sizin fonksiyonu herhangi bir bağımsız değişken (sürmüyor <void(void)>), sen gerekir bağlamak ilk (ve tek) argüman.

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

Bir işlevi parametrelerle bağlamak istiyorsanız, yer tutucular belirtmeniz gerekir:

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

Veya, derleyiciniz C ++ 11 lambdas'ı destekliyorsa:

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

( Şu anda elinizde bir C ++ 11 özellikli derleyici yok , bu yüzden bunu kontrol edemiyorum.)


1
Ben güçlendirmeye bağlı olmadığım için lambda ifadeleri kullanacağım;) Yine de teşekkürler!
Christian Ivicevic

3
@AlexB: Boost.Bind, yer tutucular için ADL kullanmaz, onları anonim bir ad alanına yerleştirir.
ildjarn

46
Küresel yakalamadan kaçınmayı [=] ve yakalananı daha net hale getirmek için [this] kullanmasını öneririm (Scott Meyers - Etkili Modern C ++ Bölüm 6. madde 31 - Varsayılan yakalama modlarından kaçının)
Max Raskin

5
Sadece küçük bir ipucu ekleyin: üye işlev işaretçisi, ilk parametre olarak std::functionekstra thisolarak , dolaylı olarak kullanılabilirstd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung

@landerlyoung: Örnek sözdizimini düzeltmek için yukarıdaki "f" olarak adlandır işlevinin adını ekleyin. Eğer isme ihtiyacınız yoksa mem_fn (& Foo :: doSomethingArgs) kullanabilirsiniz.
Val

81

Ya ihtiyacın var

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

böylece herhangi bir örnekte çağırabilirsiniz veya belirli bir örneği bağlamanız gerekir, örneğin this

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

Bu harika cevap için teşekkür ederim: D Tam olarak ne ihtiyacım var, herhangi bir sınıf örneği bir üye işlevi çağırmak için bir std :: işlevi özelleştirmek nasıl bulamadım.
penelope

Bu derleniyor, ancak standart mı? İlk argümanın olduğu garanti edildi thismi?
sudo rm -rf slash

@ sudorm-rfslash evet, sen
Armen Tsirunyan

Cevabınız için teşekkürler @ArmenTsirunyan ... bu bilgiyi nereden bulabilirim?
sudo rm -rf slash

13

Bir üye işlevini sınıf örneği olmadan depolamanız gerekiyorsa, bunun gibi bir şey yapabilirsiniz:

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

Depolama türü otomatik olmadan nasıl görünür ? Bunun gibi bir şey:

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

Bu işlev deposunu standart bir işlev bağlayıcısına da geçirebilirsiniz

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

Geçmiş ve gelecek notlar: Daha eski bir arayüz std :: mem_func vardı, ama o zamandan beri kullanımdan kaldırıldı. Üye işlevlerine işaretçi çağrılabilir yapmak için C ++ 17 sonrası bir teklif var . Bu çok hoş olurdu.


@Danh std::mem_fnedilmiş olup çıkarıldı; bir sürü gereksiz aşırı yük vardı. Öte yandan std::mem_funC ++ 11 ile kullanımdan kaldırıldı ve C ++ 17 ile kaldırılacak.
Max Truxa

@Danh Tam olarak bahsettiğim şey bu;) İlk "temel" aşırı yük hala orada: template<class R, class T> unspecified mem_fn(R T::*);ve gitmeyecek.
Max Truxa

@Danh Oku DR dikkatlice. 13 aşırı yüklenmeden 12'si DR tarafından çıkarıldı. Bu sonuncusu değildi (ve olmayacak; ne C ++ 11 ya da C ++ 14'te).
Max Truxa

1
Neden aşağı oy? Diğer her yanıt sınıf örneğini bağlamanız gerektiğini söyledi. Yansıma veya komut dosyası oluşturmak için bir ciltleme sistemi oluşturuyorsanız, bunu yapmak istemezsiniz. Bu alternatif yöntem bazı insanlar için geçerlidir ve geçerlidir.
Greg

Teşekkürler Danh, geçmiş ve gelecekteki ilgili arayüzler hakkında bazı yorumlarla birlikte düzenledim.
Greg

3

Ne yazık ki, C ++, bir nesneye ve üye işlevlerinden birine başvuran doğrudan çağrılabilir bir nesne almanıza izin vermez. &Foo::doSomethingKim üye işlev ama karşılık gelen bir "elemanı işlevi için işaretçi" veren değil ilgili nesne.

Bunun etrafında iki yol vardır, biri std::bind"işaretçiden üye işlevine" işaretçiye bağlamak için kullanmaktır this. Diğeri ise thisişaretçiyi yakalayan ve üye işlevini çağıran bir lambda kullanmaktır .

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

Ben ikincisini tercih ederim.

G ++ ile en azından bir üye işlevinin buna bağlanması, boyut olarak üç puntolu bir nesneye neden olur, bunu bir değere atamak std::functiondinamik bellek ayırmaya neden olur.

Öte yandan, yakalayan bir lambda thissadece bir işaretçi boyutundadır ve onu atamak std::function, g ++ ile dinamik bellek tahsisi ile sonuçlanmaz.

Bunu diğer derleyicilerle doğrulamasam da, benzer sonuçların orada bulunacağından şüpheleniyorum.


1

Kaputun altında daha az genel ve daha hassas bir kontrol istiyorsanız functors kullanabilirsiniz. Api mesajını bir sınıftan başka bir sınıfa yönlendirmek için win32 api ile örnek.

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

};

Kullanım prensipleri

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

İyi şanslar ve bilgi paylaşımı için herkese teşekkürler.


0

Bunu std::bindyapmaktan kaçınabilirsiniz :

std::function<void(void)> f = [this]-> {Foo::doSomething();}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.