Funktionsobjekte (auch functors genannt) haben Sie an verschiedenen Stellen im Kurs schon kennen gelernt. Trotzdem hier noch einmal eine Wiederholung.
Funktionsobjekte sind Objekte, deren Klasse den Operator ()
überlädt. Nachfolgend ist ein Beispiel für einen solchen Functor
aufgeführt, welcher den Wertebereich des an ihn übergebenen Parameters
begrenzt.
// Functor-Klasse
struct Limit
{
void operator()(int& val)
{
if (val<0)
val = 0;
else
if (val>50)
val = 50;
}
};
|
Sollen zum Beispiel alle Elemente in einem Container durchlaufen werden, so kann hierfür die Bibliotheksfunktion for_each(...) eingesetzt werden. for_each(...) besitzt folgende Deklaration in der Header-Datei <algorithm>:
| template <class InputIterator, class Function> void for_each(InputIterator first, InputIterator last, Function f); |
for_each(...) ruft für jedes Containerelement im Bereich [first...last) die Funktion oder den Functor f auf. f erhält als Parameter das zu bearbeitende Element.
// Functor-Klasse struct Limit { void operator()(int& val) { if (val<0) val = 0; else if (val>50) val = 50; } }; // Container definieren typedef std::vector<int> cType; cType cont; // Container mit Elementen füllen ... // Container-Elemente wertmäßig begrenzen std::for_each(cont.begin(), cont.end(), Limit()); |
Da der Functor im obigen Beispiel das Element per Referenz erhält,
kann er dessen Wert ändern. Beachten Sie auch, dass innerhalb der
for_each(...) Parameterklammer das Funktionsobjekt definiert wird,
d.h. Limit() ist der Aufruf des Konstruktor der Functor-Klasse
und nicht der Aufruf des Functors! Der überladene Operator ()
selbst wird intern von for_each(...) aufgerufen.
Das obige Beispiel wäre aber auch mit einer entsprechenden einfachen Funktion realisierbar. Warum also der 'Aufwand' mit einem Functor? Nun, nehmen wir einmal an, dass die Grenzwerte für die Container-Elemente nicht zur Compilezeit bekannt sind sondern sich erst zur Laufzeit ergeben. Da for_each(...) aber nur einen Parameter an die Funktion/Functor übergibt, nämlich das aktuelle Elemente, kann diese Anforderung nicht mehr mit einer Funktion gelöst werden, sondern nur noch über einen Functor. Sehen wir uns an, wie dieses Problem mithilfe des Functors gelöst wird.
Da Functor-Klassen 'normale' Klassen sind, nur eben mit überladenem
()-Operator, können diese Klassen auch einen Konstruktor
besitzen. Im Beispiel unten wurde die Klasse um einen Konstruktor
erweitert, der als Parameter die Unter- und Obergrenze des erlaubten
Bereichs erhält. Da der Functor innerhalb der Parameterklammer von
for_each(...) definiert wird, muss diese Definition lediglich
entsprechend angepasst werden, so wie dargestellt. Und schon können Sie
die Grenzwerte beliebig zur Laufzeit festlegen.
// Functor-Klasse class Limit { int lower,upper; // Grenzwerte public: // ctor Limit(int l, int u): lower(l), upper(u) {} // Functor void operator()(int& val) { if (val<lower) val = lower; else if (val>upper) val = upper; } }; // Container definieren wie oben ... // Container-Elemente begrenzen, Grenzwerte an ctor übergeben std::for_each(cont.begin(), cont.end(), Limit(10,50)); |
Predicates sind Funktionen oder Functors die einen bool-Wert (oder einen in bool konvertierbaren Wert) zurückliefern. Solche Predicates spielen innerhalb der Algorithmen der Standard-Bibliothek eine wichtige Rolle, da sie in Abhängigkeit von einer Bedingung eine Aktion in den Algorithmen steuern. So entfernt z.B. der Algorithmus remove_if(...) Elemente aus einen Container, die einer bestimmten Bedingung genügen.
Die Bedingung ist als Predicate zu definieren. Liefert das Predicate true zurück, so wird das an das Predicate übergebene Element aus dem Container entfernt. Wenn Sie ein Predicate definieren, sollten Sie aber immer darauf achten, dass dieses teilweise innerhalb der Algorithmen kopiert wird und sich das Verhalten dadurch nicht ändern sollte. Das Predicate Mod im Beispiel liefert dann true zurück, wenn der übergebene Wert val sich ohne Rest durch modValue teilen lässt. Würde dieses Predicate innerhalb des remove_if(...) Algorithmus eingesetzt werden, so würden alle Element, die sich ohne Rest teilen lassen, aus dem zu bearbeitenden Container entfernt werden.
// Functor-Klasse, Predicate
class Mod
{
int modValue;
public:
Mod(int mv): modValue(mv)
{}
bool operator()(int val)
{
return val % modValue ? false : true;
}
};
|
Die C++ Standard Bibliothek stellt eine Reihe von vordefinierten Funktionsobjekte zur Verfügung.
| Functor | Aktion |
| negate<DTYP>() | - param |
| plus<DTYP>() | param1 + param2 |
minus<DTYP>() |
param1 - param2 |
multiplies<DTYP>() |
param1 * param2 |
divides<DTYP>() |
param1 / param2 |
modulus<DTYP>() |
param1 % param2 |
equal_to<DTYP>() |
Predicate param1 == param2 |
not_equal_to<DTYP>() |
Predicate param1 != param2 |
less<DTYP>() |
Predicate param1 < param2 |
less_equal<DTYP>() |
Predicate param1 <= param2 |
greater<DTYP>() |
Predicate param1 > param2 |
greater_equal<DTYP>() |
Predicate param1 >= param2 |
logical_not<DTYP>() |
Predicate !param |
logical_and<DTYP>() |
Predicate param1 && param2 |
logical_or<DTYP>() |
Predicate param1 || param2 |
Sämtliche vordefinierten Functors liegen im Namensraum std und sind in der Header-Datei functional definiert.
Alle Funktionsobjekte erhalten ihre Parameter als const-Parameter übergeben und liefern das Ergebnis der Aktion zurück. D.h. Sie können z.B. den Functor negate<DTYP> nicht mit for_each(...) verwenden um das Vorzeichen aller Elemente eines Containers umzudrehen, da das Element als Konstante übergeben wird und for_each(...) den Returnwert der aufgerufenen Funktion/Functor nicht auswertet.
Übrigens, der Functor less<DTYP> ist das Standard-Sortierkriterium für sortierte Container. Es sortiert die Elemente in einem Container in aufsteigender Reihenfolgen.
Wollen Sie zum Beispiel ein set mit strings in fallender Reihenfolge sortieren, so können Sie bei der Definition des Sets den Functor greater<string> angeben.
// string-set mit fallender Sortierung
typedef set<std::string, greater<string> > sType;
sType stringSet;
|
Mehr zur Anwendung dieser vordefinierten Functors bei der Behandlung der Algorithmen und in den nächsten beiden Lektionen.
Auch die Funktionsadapter gehören zu den vordefinierten Functors. Funktionsadapter ermöglichen die Kombination von Functors mit weiteren Functors, Werten oder speziellen Funktionen.
Folgende Funktions-Adapter stehen zur Verfügung und sind in der Header-Datei functional definiert:
| Funktions-Adapter | Beschreibung |
| bind1st(operation,wert) | führt operation(wert,param) aus und liefert das Ergebnis zurück |
| bind2nd(operation,wert) | führt operation(param,wert) aus und liefert das Ergebnis zurück |
| not1(operation) | führt !operation(param) aus und liefert das Ergebnis zurück |
| not2(operation) | führt !operation(param1,param2) aus und liefert das Ergebnis zurück |
Die Funktions-Adapter bind1st(...) und bind2nd(...) erlauben den Einsatz von Functors mit 2 Parametern in Algorithmen, die ansonsten nur Functors mit einem Parameter zulassen.
Sehen wir uns dies anhand eines Beispiels an. Der Algorithmus count_if(...)
| template<class InputIterator, class Predicate> typename iterator_traits<InputIterator>::difference_type count_if(InputIterator first, InputIterator last, Predicate pred); |
ruft für die Elemente im Bereich [first...last) das Predicate pred auf und zählt, wie oft der Wert true zurückgeliefert wird. Das Predicate pred erhält hierbei das zu untersuchende Element übergeben. D.h. mithilfe dieses Algorithmus können Sie die Anzahl der Elemente bestimmen, die einer bestimmten Bedingung genügen.
Nehmen wir nun einmal an, Sie wollen in einem int-Vektor die Elemente zählen, deren Wert kleiner 7 ist. Da das Predicate nur das aktuelle Element erhält, müssen Sie eine eigene Functor-Klasse, wie nachfolgend dargestellt, definieren. Der Vergleichswert ist im Beispiel fest vorgegeben. Alternativ könnten Sie den Vergleichswert auch zur Programmlaufzeit angeben, indem Sie der Klasse einen entsprechenden Konstruktor mitgeben
// Functor-Klasse mit Predicate struct Less7 { bool operator()(int val) { return val<7; } }; ... // Anzahl der Elemente mit Wert < 7 im Container cont zählen result = count_if(cont.begin(), cont.end(), Less7()); |
Durch den Einsatz eines Funktions-Adapters können Sie sich aber den zusätzlichen Aufwand einer eigenen Functor-Klasse sparen. Die Standard-Bibliothek enthält, wie bereits erwähnt, die Functor-Klasse less<> um Elemente auf kleiner-als zu vergleichen. Dieser Functor erhält dabei als Parameter die beiden zu vergleichenden Elemente übergeben. Damit unser obiger Vergleich mit diesem Functor durchführbar ist, müssen wir irgendwie den Fix-Wert 7 als zweiten Parameter an less<> übergeben. Und genau dies wird durch den Funktions-Adapter bind2nd(...) erreicht. bind2nd(...) ruft den im ersten Parameter spezifizierten Functor auf und übergibt dem Functor das aktuelle Container-Element im ersten Parameter und den Wert im zweiten Parameter.
Somit kann der Aufruf des count_if(...) Algorithmus wie
nebenstehend dargestellt umgeschrieben werden. Der less<> Functor
führt damit den Vergleich element<7 durch.
// Anzahl der Elemente mit Wert < 7 im Container cont zählen
result = count_if(cont.begin(), cont.end(), bind2nd(less<int>(),7));
|
Würde anstelle des bind2nd(...) Adapters der bind1st(...)
Adapter verwendet werden, so würde dies zur Vergleichsoperation 7
< element im less<> Functor führen.
// Anzahl der Elemente mit 7 < Wert im Container cont zählen
result = count_if(cont.begin(), cont.end(), bind1nd(less<int>(),7));
|
Die Adapter not1(...) bzw. not2(...) negieren das
Ergebnis des als Parameter übergebenen Functors. Wird dieser Adapter
z.B. in Verbindung mit dem obigen bind2nd(...) eingesetzt, so
liefert count_if(...) jetzt die Elemente die nicht kleiner 7
sind, also die Elemente, für die element >= 7 gilt.
// Anzahl der Elemente mit Wert >= 7 im Container cont zählen
result = count_if(cont.begin(),cont.end(), not1(bind2nd(less<int>(),7)));
|
Außer diesen Funktions-Adaptern stellt die Standard-Bibliothek noch Adapter zur Verfügung, um Memberfunktionen innerhalb von Algorithmen einsetzen zu können:
| Adapter | Beschreibung |
| mem_fun_ref(memfct) | führt memfct() des Container-Objekts aus |
| mem_fun(memfct) | führt memfct() des Container-Objektzeigers aus |
Um den Einsatz dieser beiden Adapter zu demonstrieren, sei die nachfolgend dargestellte Klasse Any vorgegeben. Objekte dieser Klasse sollen nun in einem Container abgelegt werden und dann für jedes dieser Container-Objekte dessen Memberfunktion PrintName() aufgerufen werden. Da alle Objekte im Container durchlaufen werden sollen, wird auch hier der for_each(...) Algorithmus eingesetzt. for_each(...) erlaubt aber für die auszuführende Operation nur die Angabe einer normalen Funktion, eines Functors oder einer statischen Memberfunktion, aber nicht die Angabe einer normalen Memberfunktion. Um nun die Memberfunktion PrintName() innerhalb von for_each(...) einsetzen zu können, wird der Adapter mem_fun_ref(memfct) eingesetzt. for_each(...) selbst ruft dann den Adapter mem_fun_ref(...) mit dem aktuellen Objekt auf und mem_fun_ref(...) dann die entsprechende Memberfunktion des übergebenen Objekts.
class Any
{
std::string data;
public:
Any(const std::string& s): data(s)
{}
void PrintName() const
{
cout << data << endl;
}
};
// Objekt-Container
vector<Any> objCont;
...
// Fuer jedes Container-Objekt dessen Memberfunktion PrintName() aufrufen
for_each(objCont.begin(), objCont.end(), mem_fun_ref(&Any::PrintName));
|
Sind in einem Container keine Objekte sonder Objektzeiger abgelegt, so muss anstelle von mem_fun_ref(...) der Adapter mem_fun(...) verwendet werden.
class Any
{
...
};
// Objektzeiger-Container
vector<Any*> ptrCont;
...
// Fuer jedes Container-Objekt dessen Memberfunktion PrintName() aufrufen
for_each(ptrCont.begin(), ptrCont.end(), mem_fun(&Any::PrintName));
|
Wie bereits erwähnt, lassen sich solche Adapter auch kombinieren.
Soll für Container-Objekte eine Memberfunktion aufgerufen werden, die einen Parameter besitzt, so können Sie z.B. den Adapter mem_fun_ref(...) mit dem Adapter bind2nd(...) kombinieren. Im Beispiel wird die Memberfunktion Print(...), die einen const-char* Parameter besitzt, für jedes Any-Objekt im Container objCont aufgerufen. Der zusätzliche const-char* Parameter der Memberfunktion wird über bind2nd(...) an die Memberfunktion übergeben.
class Any
{
...
void Print(const char* header) const
{
cout << header << data << endl;
}
};
// Objektzeiger-Container
vector<Any> objCont;
...
// Fuer jedes Container-Objekt dessen Memberfunktion Print() aufrufen
for_each(objCont.begin(), objCont.end(), bind2nd(mem_fun_ref(&Any::Print),"Name: "));
|
So, beenden wir an dieser Stelle die Behandlung von Funktionsobjekten und wenden uns in den nächsten beiden Lektion den Algorithmen zu. Dort werden Sie auch einige der aufgeführten vordefinierten Functors wieder finden.