c++ lamda mit context

Gast #5876756
Lesenswert?

Hallo,

ich würde meinen Code gern etwas event-driven haben, daher will ich 
meinen Objekten gerne Methoden übergeben, dass sie sich zurück melden 
können, wenn etwas passiert. Leider habe ich schon seit bestimmt 10 
Jahren kein c++ mehr gemacht und benötige daher etwas hilfe. Kann mir 
jemand sagen, wie folgender JavaScript-Code in c++ aussehen würde?

1
doSomething(obj){
2
  const a = 5;
3
  obj.doAlsoSomething(()=>{ // pass a function as parameter to another function
4
    console.log(a); // prints 5
5
  });
6
}

Soweit bin ich schon. Ich habe etwas gelsen dass man mit std:function 
den Context mit übergeben kann, aber gibt es auch andere alternativen? 
Ich will es auf dem Arduino laufen lassen und nicht unnötig platz mit 
std verschwenden.
1
class DebouncedButton
2
{
3
private:
4
  void (*onToggle)(int); // called, when the debounced state of the button changes
5

6
public:
7
  void begin(int buttonPin, void (*callback)(int))
8
  {
9
    this->buttonPin = buttonPin;
10
    this->onToggle = cb;
11
    pinMode(buttonPin, INPUT);
12
  }
13
//...
14
}
15

16

17
void setup()
18
{
19
  for (int i = 0; i < PerformState::BTN_COUNT; i++)
20
  {
21
    buttons[i] = new DebouncedButton();
22
    buttons[i]->begin(BTNS[i], /* ?????? */);
23
  }
24
}

Danke für eure Hilfe.

Mfg,
Jan
(Firma: Torrox.de) #5876767
Lesenswert?

Jan schrieb:

>
1
> doSomething(obj){
2
>   const a = 5;
3
>   obj.doAlsoSomething(()=>{ // pass a function as parameter to another 
4
> function
5
>     console.log(a); // prints 5
6
>   });
7
> }
8
>
1
template < typename T >
2
void doSomething(T& obj){
3
    const int a = 5;
4
    obj.doAlsoSomething([a](){
5
        std::cout << a << std::endl; 
6
    });
7
}

> Soweit bin ich schon. Ich habe etwas gelsen dass man mit std:function
> den Context mit übergeben kann, aber gibt es auch andere alternativen?

std::function<> ist im wesentlichen eine Abstraktion für etwas, dass 
aufrufbar ist. Zusätzliche Parameter (Kontext) kannst Du mit std::bind() 
an einen bestehendes aufrufbares Ding packen.

> Ich will es auf dem Arduino laufen lassen und nicht unnötig platz mit
> std verschwenden.

Die Anforderungen sind zu schwamming und die Möglichkeiten, die C++ 
bieten zu groß, um da jetzt irgend eine Empfehlung draus zu machen.

mfg Torsten
Gast #5877570
Lesenswert?

Closures in einer Sprache, die Garbage Collection mit überladenen 
Zuweisungsoperatoren simuliert? Hört sich so an, als hätte die C++ 
Fraktion eine neue Methode gefunden, sich selbst in den Fuss zu 
schiessen.

Kannst du uns berichten, wie die Sache ausgegangen ist?
Gast #5877943
Lesenswert?

Jan schrieb:
> Ich will es auf dem Arduino laufen lassen und nicht unnötig platz mit
> std verschwenden.

Du initialisierst die Buttons in einer Schleife. Ich kann mir nicht
vorstellen, dass das so bleiben wird. Denn du wirst ja nicht 
PerformState::BTN_COUNT Knöpfe haben, die alle das gleiche machen, oder?

Packe die Sache andersrum an: Schreibe erst mal die Initialisierungen 
hintereinander hin, und erst wenn sich ein Muster ergibt, mache eine 
Schleife daraus. Ich bin mir ziemlich sicher, dass sich kein 
vernünftiges ergeben wird.

Insb. in einer Firmware wird es immer der Fall sein, dass absolute 
Generizität nichts bringt, weil im konkreten Anwendungsfall die 
angeschlossene Hardware fix ist. Du schreibst ja nicht eine Software, 
die mal mit drei Buttons und ein andermal mit fünf Buttons funktionieren 
muss. Du kannst also ruhig alle PerformState::BTN_COUNT Button-Treiber 
(DebouncedButton-Instanzen) explizit hinschreiben. Sobald du das gemacht 
hast, kannst du jeder einzelnen Instanz auch eine eigene Klasse geben. 
Dann ergibt sich wieder ein Muster, das man diesmal mit Templates 
erledigen kann. Und schon brauchst du kein std::function<> mehr:
1
template<class CB, int PIN>
2
class DebouncedButton
3
{
4
private:
5
  CB onToggle; // called, when the debounced state of the button changes
6

7
public:
8
  DebouncedButton(CB&& cb) : onToggle(std::forward<CB>(cb))
9
  {
10
    pinMode(PIN, INPUT);
11
  }
12
//...
13
}
Oje, jetzat weiss ich nicht mehr weiter, weil mir das Arduino-Framework 
mit seinem bescheuerten setup()+loop()-Konzept in die Suppe spuckt: Man 
muss die Button-Instanzen globale Variablen machen, und ob das in deinem 
Fall möglich ist, kann ich durch meine Kristallkugel leider nicht sehen. 
Aber vielleicht hab' ich dir ein paar Denkanstöße gegeben.
Persönliche Seite #5877966
Lesenswert?

Der Vollständigkeit halber, so ginge es in "normalem" C++:
1
#include <functional>
2
#include <utility>
3

4
class DebouncedButton
5
{
6
private:
7
  std::function <void(int)> onToggle;
8
  int buttonPin;
9
public:
10
  DebouncedButton(int buttonPin_, std::function<void(int)> cb)
11
    : onToggle (std::move (cb)), buttonPin (buttonPin_)
12
  {
13
    pinMode(buttonPin, INPUT);
14
  }
15
};
16

17
void setup()
18
{
19
  DebouncedButton* buttons [1];
20
  buttons[0] = new DebouncedButton (42, [&] (int x) {
21
    std::cout << "Callback: " << x << std::endl;
22
  });
23
}

Das "this->" ist in C++ meist überflüssig.
Allerdings sind weder "new" noch "std::function" besonders gut für 
Mikrocontroller geeignet, weil dynamischer Speicher hier meist zu viel 
Overhead hat. Dazu kommt, dass der AVR-GCC und damit auch AVR-Arduino 
keine C++-Standard-Bibliothek mitliefert, also auch kein std::function. 
Daher ist die Lösung von tictactoe wohl die bessere. Ansonsten könnte 
man auch ein klassisches Observer-Pattern mit virtuellen Funktionen 
machen.

Allerdings:

tictactoe schrieb:
> DebouncedButton(CB&& cb) : onToggle(std::forward<CB>(cb))
Das geht so leider nicht; dazu müsste "CB" ein template-Parameter des 
Konstruktors sein:
1
template <typename CB2>
2
DebouncedButton(CB2&& cb) : onToggle(std::forward<CB2>(cb))
Was aber blöd ist weil man dann zu viel übergeben kann. Alternativ so 
einzeln überladen:
1
DebouncedButton(const CB& cb) : onToggle(cb) {}
2
DebouncedButton(CB&& cb) : onToggle(std::move (cb)) {}
(Firma: Torrox.de) #5884145
Lesenswert?

Schamane schrieb:
> Niklas G. schrieb:
>> [&]
>
> Was bedeutet denn das?

Das ist die capture clause zum Lambda. [&] gibt an, dass der default, 
mit dem der Codeblock des Lambdas auf andere lokale Variablen zugreift, 
per Reference ist.

Im Beispiel oben, ist es überflüssig, da nur auf den Parameter X und die 
globale Variable std::cout zugegriffen wird.

Wenn lambdas als Callback eingesetzt werden, will man in der Regel auch 
nicht per Reference auf lokale Variablen zugreifen, weil die Lebenszeit 
des Lambdas in dem Fall in der Regel länger ist, als die Funktion, in 
dem das Lambda erzeug wurde.

> Mein C++ Wissensstand ist auf dem Stand von irgendwo Ende der 90er.

In den 20 Jahren ist sehr viel passiert.
Gast #5884214
Lesenswert?

Der Aufwand, die Lebenszeit der referenzierten Daten herauszufinden, 
übersteigt den Nutzen.

Wir nehmen C++, weil es für jedes Problem eine passende Library gibt. 
Nur leider hat jede Library eine andere Strategie zur Freigabe des 
Speichers. Leider funktionieren Unit Tests nicht. In den einfachen Tests 
haben dangling pointers keine Auswirkungen. Wir müssen die internen 
Details von einem Dutzend verschiedener Container-Libraries beherrschen.

Mag ja sein, dass es wunderbar funktioniert, wenn wir uns auf die 
modernen Konzepte beschränken. Aber wenn wir die Unmassen an bestehenden 
Libraries und Frameworks nicht benutzen - warum nehmen wir dann C++?

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren