mikrocontroller.net

Forum: PC-Programmierung c++ lamda mit context


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Jan (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

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

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.
class DebouncedButton
{
private:
  void (*onToggle)(int); // called, when the debounced state of the button changes

public:
  void begin(int buttonPin, void (*callback)(int))
  {
    this->buttonPin = buttonPin;
    this->onToggle = cb;
    pinMode(buttonPin, INPUT);
  }
//...
}


void setup()
{
  for (int i = 0; i < PerformState::BTN_COUNT; i++)
  {
    buttons[i] = new DebouncedButton();
    buttons[i]->begin(BTNS[i], /* ?????? */);
  }
}

Danke für eure Hilfe.

Mfg,
Jan

: Verschoben durch Moderator
Autor: Torsten R. (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jan schrieb:

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

> 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

Autor: Panik (Gast)
Datum:

Bewertung
-5 lesenswert
nicht 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?

Autor: M.K. B. (mkbit)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: tictactoe (Gast)
Datum:

Bewertung
1 lesenswert
nicht 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:
template<class CB, int PIN>
class DebouncedButton
{
private:
  CB onToggle; // called, when the debounced state of the button changes

public:
  DebouncedButton(CB&& cb) : onToggle(std::forward<CB>(cb))
  {
    pinMode(PIN, INPUT);
  }
//...
}
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.

Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Der Vollständigkeit halber, so ginge es in "normalem" C++:
#include <functional>
#include <utility>

class DebouncedButton
{
private:
  std::function <void(int)> onToggle;
  int buttonPin;
public:
  DebouncedButton(int buttonPin_, std::function<void(int)> cb)
    : onToggle (std::move (cb)), buttonPin (buttonPin_)
  {
    pinMode(buttonPin, INPUT);
  }
};

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

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:
template <typename CB2>
DebouncedButton(CB2&& cb) : onToggle(std::forward<CB2>(cb))
Was aber blöd ist weil man dann zu viel übergeben kann. Alternativ so 
einzeln überladen:
DebouncedButton(const CB& cb) : onToggle(cb) {}
DebouncedButton(CB&& cb) : onToggle(std::move (cb)) {}

: Bearbeitet durch User
Autor: Schamane (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Niklas G. schrieb:
> [&]

Was bedeutet denn das?

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

Autor: Schamane (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Torsten R. (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Panik (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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++?

Autor: Gartenbahner (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Panik schrieb:
> Garbage Collection mit überladenen Zuweisungsoperatoren simuliert

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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.