Table of Contents
Motivation
Auf meiner Lernreise in die Welt der Robotik ist eines meiner Ziele, mein angestaubtes C++-Wissen aufzubessern. Heute schauen wir uns das Konzept von Lambdas an.
Beispiel sort
Angenommen, wir wollen einen std::vector sortieren, dann können wir das folgendermaßen machen:
std::vector<float> numbers = { -3.5, 1.0, -2.0, -11.0};
std::sort(numbers.begin(), numbers.end());
Der Vector wird hier inplace aufsteigend sortiert.
-11.0 < -3.5 < -2.0 < 1.0
Was aber, wenn wir absteigend sortieren wollen? Bis einschließlich C++03 musste dazu eine Komparator-Funktion definiert werden, die via Funktionszeiger an die sort-Funktion übergeben wird
bool sort_descending(const float a, const float b)
{
return a > b;
}
std::sort(numbers.begin(), numbers.end(), sort_descending);
Nachteile Funktionszeiger
Diese Funktionen müssen außerhalb der main Funktion und außerhalb von Klassen definiert werden, da nested functions von C++ nicht unterstützt werden und Methoden nicht (einfach) als Funktionszeiger verwendet werden dürfen. „Externe“ Funktionen können aber nachteilig für die Wartbarkeit einer Code-Basis sein.
Lambdas for the rescue
Jetzt kommen die Lambdas ins Spiel. Lambdas sind anonyme Funktionen, die keinen Funktionsnamen besitzen.
Beispiel mit sort
Bei einem Lambda-Ausdruck kann der Komparatorcode innerhalb des sort-Funktionsaufrufes definiert werden:
std::sort(numbers.begin(), numbers.end(), [] (const float a, const float b) {return a > b;});
Anatomie eines Lambdas
Hier siehst du den Aufbau eines Lambda-Befehls:
[capture list](parameter) -> return_type { body }
Capture-List
Die Capture List sagt dem Compiler, welche Variablen aus dem umgebenden Kontext (bspw. lokale Variablen in main) in das Lambda übernommen werden sollen.
Wie wir später sehen werden, ist es oft auch eine gute Idee, den this-Zeiger in der Capture-List einzufangen, da das die Lesbarkeit erhöht.
Parameter
Wie bei einer normalen Funktion sind die Parameter die Platzhalter für die Argumente, die an die Funktion übergeben werden sollen und die in der Funktion verwendet werden können.
Return-Type
Der Compiler kann in den meisten Fällen den Rückgabetyp des Lambdas herleiten, deswegen muss er nicht immer explizit angegeben werden.
Funktionsrumpf
Im Rumpf der Funktion können nun sowohl die gecapturerten Variablen, als auch die Parameter verwendet werden.
Der Return-Wert muss zum return-type passen, falls dieser explizit angegeben wurde.
Named Generic Lambdas
Seit C++14 kann ein Lambda-Ausdruck auch einer Variablen zugewiesen werden. Es muss hier das auto keyword verwendet werden:
auto twice = [] (const auto& x) {
return 2*x;
};
double ist jetzt ein Lambda-Objekt, das auch innerhalb einer anderen Funktion definiert werden kann.
Aufgerufen wird es nun wie eine herkömmliche Funktion:
std::cout << twice(4);
std::bind vs lambda
Zurück zur Robotik.
std:bind ist eine Möglichkeit in C++, Methoden einer Klasse als callback-Funktionen zu verwenden. Dies wird gerne in ROS-Nodes verwendet, um zum Beispiel Timer-Callbacks zu realisieren. Hier ein Beispiel aus dem sehr guten ROS-Tutorial von Eduard Rennard:
timer = this->create_wall_timer(std::chrono::milliseconds(1000), std::bind(&RobotNewsStationNode::publish_message, this));
Das sieht für das ungeübte Auge etwas krude aus. Das liegt daran, dass Methoden (also Funktionen einer Klasse) keine Function Pointer sein können. Als Alternative können hier auch Lambdas benutzt werden:
timer = this->create_wall_timer(std::chrono::milliseconds(1000), [this] { publish_message(); });
Das wirkt schon deutlich übersichtlicher. Die Capture-List bringt den this-Zeiger in den Scope des Lambdas, deshalb kann im Funktionsrumpf direkt die publish_message Methode aufgerufen werden.
Lambdas mit Parametern
Wenn an den Callback auch Parameter übergeben werden sollen, wird es mit std::bind noch unübersichtlicher, weil hier der Platzhalter für Funktionsargumente _1 benutzt werden muss:
using namespace std::placeholders;
using example_interfaces::msg::String;
subscriber = this->create_subscription("/talker", 10, std::bind(&MyListener::listener_callback, this, _1));
Auch hier kann ein Lambda-Ausdruck wieder etwas Lesbarkeit zurückbringen:
subscriber = this->create_subscription("/talker", 10, [this](const String &msg) { listener_callback(msg);});




