Tagfüggvény átadása API-nak, std function callback függvény
2017-09-14T10:16:46+02:00
2017-09-14T19:25:44+02:00
2022-08-10T19:15:30+02:00
TiPeter
Sziasztok!

Az Intel TinyB Bluetooth LE API-t próbálom használni Linux alatt, C++-szal.
Készítettem egy osztályt, ami Bluetooth LE kapcsolatot épít fel megadott paramétereknek (ID, MAC) megfelelő eszközökkel, és periodikusan adatot fogad tőlük.
Az API BluetoothManager osztályának ::find tagfüggvényében meg lehet adni egy callback függvényt, ami meghívódik, ha a megadott paraméterű BLE eszköz megjelenik.

A ::find prototípusa:

std::weak_ptr<BluetoothEvent> tinyb::BluetoothManager::find (BluetoothType type, std::string *name, std::string *identifier, BluetoothObject *parent, BluetoothCallback cb, bool execute_once = true, std::chrono::milliseconds timeout = std::chrono::milliseconds::zero());
A BluetoothCallback definíciója:

typedef std::function<void (BluetoothObject &, void *)> BluetoothCallback;
A cél az lenne, hogy ha az API talál egy új BLE eszközt, az osztályom adott tagfüggvényét hívja meg callback függvényként.
Az osztályban létrehoztam egy BluetoothCallback-nak megfelelő tagfüggvényt, és azt próbáltam std::bind segítségével megadni (a 'this'-paraméter miatt), de nem sikerült.
Kényszermegoldásként az osztályom 'connectToDevice'-tagfüggvényében létrehozok egy lambda függvényt, ami meghívja a kívánt tagfüggvényemet; és ezt a lambdát adom át az API-nak.

Így most működik, de úgy érzem, nem ez a jó megoldás: pl. a tagfüggvényben létrehozott lambda, ami át lett adva külső API-nak, a függvény lefutásakor kikerül a scope-ból... Tényleg, ez hogyan van? :)

Szóval, a kérdés: szerintetek mi lenne az elegáns és modern megoldás a problémára?

Köszi előre is az ötleteket!
Mutasd a teljes hozzászólást!

  • >  Szóval, a kérdés: szerintetek mi lenne az elegáns és modern megoldás a problémára?

    Statikus függvény (vagy méginkább külön függvény) használata callback-ként, paraméterként átadva az ojjektum címét. Persze ebben az esetben ez nem megy.

    Kiegészítő olvasmány: http://web.axelero.hu/lzsiga/cback.cc
    Mutasd a teljes hozzászólást!
  • Egy jó C programozó minden nyelvben tud C-ben programozni. (C++-ban is...)
    Mutasd a teljes hozzászólást!
  • Nem tudom, melyik verzióját használod a libnek, de a github-on, ha megnézed a forráskódot, láthatod, hogy a callback (cb) paramétert le se sza nem is veszi figyelembe.
    Mutasd a teljes hozzászólást!
  • Igen, észrevettem...
    A github-os verziót használom, de a BluetoothManager.cpp-be kicsit belejavítottam... :)

    Ha van "standard" modern C++ megoldás, az érdekelne leginkább.. Sajnos nekem elfogytak az ötleteim.
    Mutasd a teljes hozzászólást!
  • Az alapján, amit eddig elmondtál, csak a lambda (vagy annak kézi implementációja) marad járható útnak. Ha azért aggódsz, hogy nem stimmel a lambda élettartama, akkor rakd valami olyan helyre, ahol garantáltan "túléli" a callback hívásig. Kézenfekvő lehet magába az objektumba rakni egy mezőt, ami a lambda példányt tárolja, de ha nem akarsz minden példányodban erre helyet vesztegetni, akkor lehet valami globális konténerbe pakolgatni őket (amiből persze illik kivenni is őket, ha már nincs a lambdára szükség, de legkésőbb a példány felszabadítása előtt).
    Mutasd a teljes hozzászólást!
  • Ez igaz, sőt: egy jó programozó minden kontextusban a legegyszerűbb, legflexibilisebb, legkompatibilisebb megoldást keresi... Aztán vannak lelkes fiatal kollégák, akik szerint lambda és boast nélkül mit sem ér egy fejlesztés... (Ahogy Cafrinka mondta volt a Szaffiban: a legényke még zöldecske, de majd megérik estére!)
    Mutasd a teljes hozzászólást!
  • A callback megoldásoknál (mint jelen esetben is), szokott lenni egy void* típusú "user defined" paraméter, amit arra használ a kliens, amire jónak látja. Ha belefér, hogy belejavíts a kódba, akkor például a *this értékét is átadhatod és ezzel már tetszőleges tagfüggvényt meg tudsz hívni. (A githubos kódban erre még nem látom, hogy lenne támogatás, ezt is neked kellene beletenni.)
    Mutasd a teljes hozzászólást!
  • Természetesen átlehet passzolni member fuknciót is.

    bind( &ClassName::member_func, &obj, _1, _2)
    Így próbáltad?
    Mutasd a teljes hozzászólást!
  • Másik lehetséges megoldás, ha sok helyen szükség van a callback regisztrálására, akkor létrehozol egy public functiont:

    auto getCallback() { return bind(&ClassName::member_func, this, _1, _2); }

    Utána csak simán az 'obj.getCallback()'-eket használod paraméter átadásnál..
    Mutasd a teljes hozzászólást!
  • Hát, az az igazság, hogy pár éve én is olyan szép C kódokat írtam Java-ban és C++-ban, hogy öröm volt nézni... :)
    A statikus függvény kézenfekvő megoldás; így próbáltam ki, hogy egyáltalán működik-e az api szolgáltatása (nem... :) ). Viszont szeretnék "szép" C++11 kódot írni...
    Mutasd a teljes hozzászólást!
  • auto getCallback()..
    Ez a megoldás C++14-et kíván, C++11-ben kézzel meg kell fejteni a return type-ot, amire amúgy van már typedef-ed a 'BluetoothCallback'
    Mutasd a teljes hozzászólást!
  • Köszi a segítségeteket, bind-del sikerült! Kiderült, hogy benéztem a this-t... Az osztály PIMPL minta szerint készült, és a callback függvény a belső implementációs osztály tagfüggvénye, én meg csak this-t adtam meg... Amatőr hiba.

    Még egy kérdésem lenne:
    A lenti példában std::function-ban tárolok lambda függvényeket. Mindkét megoldás helyes?
    Az első esetben hol jön létre a lambda? A stacken? Ha a konstruktor lefutott, akkor kikerül a scope-ból, mi történik? Vagy ez analóg azzal, mintha egy string-nek adnék értéket?
    Tudom, hogy ezek amatőr kérdések, de nekem nem teljesen tiszta...

    class Foo { public: Foo() { m_func = std::function<void (const std::string& text, int num)>( [](const std::string &text, int num) { std::cout << "text: " << text << "\tnum: " << num << std::endl; }); m_funcP = new std::function<void (const std::string& text, int num)>( [](const std::string &text, int num) { std::cout << "text: " << text << "\tnum: " << num << std::endl; }); } ~Foo() { delete m_funcP; } void useFunc(const std::string& text, int num) { m_func(text, num); } void useFuncP(const std::string& text, int num) { (*m_funcP)(text, num); } private: std::function<void (const std::string& text, int num)> m_func; std::function<void (const std::string& text, int num)> *m_funcP = nullptr; }; int main() { Foo fooObj; fooObj.useFunc("Hello World", 10); fooObj.useFuncP("Pointer to std::function", 42); return 0; }
    Köszi!
    Mutasd a teljes hozzászólást!
  • Ugyan az történik a lambdával, mint itt, az std::function tárol egy másolatot.

    struct Lambda { void operator()(const std::string &text, int num) const { std::cout << "text: " << text << "\tnum: " << num << std::endl; } }; m_func = std::function<void (const std::string& text, int num)>(Lambda()); m_funcP = new std::function<void (const std::string& text, int num)>(Lambda());
    Mutasd a teljes hozzászólást!
  • Igen mind a kettő a helyes. Bár a példádban nem látom sok értelmét a pointereznésnek, csak extra hibalehetőséget visz a dologba.

    Az std::function mindent menedzsel magának, pontosan nem tudom, hogy milyen megkötések vannak az implementációjára, de a belső felépítése mindenképpen implementáció függő.

    Itt találsz néhány részletet:
    std::function

    "When the target is a function pointer or a std::reference_wrapper, small object optimization is guaranteed, that is, these targets are always directly stored inside the std::functionobject, no dynamic allocation takes place. Other large objects may be constructed in dynamic allocated storage and accessed by the std::function object through a pointer.

    "Vagy ez analóg azzal, mintha egy string-nek adnék értéket?"
    Ilyen szempontból tekintheted analógnak az std::string-el.
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd