Forum: PC-Programmierung c++ lamda mit context


von Jan (Gast)


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

: Verschoben durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


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

von Panik (Gast)


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?

von M.K. B. (mkbit)


Lesenswert?

Für das Callback würde ich eine std::function nehmen. Als Callback 
kannst du dieser dann entweder eine bestehende Funktion mit std::bind 
oder ein lokales Lambda übergeben.
Ich meine für beidest musst du den Compiler mindestens auf C++11 
stellen.

von tictactoe (Gast)


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.

von Niklas G. (erlkoenig) Benutzerseite


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

: Bearbeitet durch User
von Schamane (Gast)


Lesenswert?

Niklas G. schrieb:
> [&]

Was bedeutet denn das?

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

von Schamane (Gast)


Lesenswert?

Hab was gefunden: Die modischen Hippster-Vollbart-Lamdas die jetzt in 
jeder Sprache Einzug halten:
https://stackoverflow.com/questions/39789125/what-does-mean-before-function

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


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.

von Panik (Gast)


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++?

von Gartenbahner (Gast)


Lesenswert?

Panik schrieb:
> Garbage Collection mit überladenen Zuweisungsoperatoren simuliert

:) gefällt mir! Ein Troll mit hohem Niveau!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.