C++ ist keine funktionale Programmiersprache. C++ hat seine Wurzeln in der prozeduralen und objektorientierten Programmierung. Um so verwunderlicher ist es, dass die Programmierung im funktionalen Stil in modernem C++ immer wichtiger wird. Dies Phänomen trifft aber nicht nur auf C++ zu. Auch Python hat schon sehr viele funktionalen Feature angenommen und selbst Java besitzt mittlerweile Lamba-Funktionen.
Am Anfang stand(en) die Frage(n)
Am Anfang stehen zuerst einmal viele Fragen zur funktionalen Programmierung in C++:
- Welche funktionalen Feature bietet C++ an?
- Warum sind rein funktionale Programmiersprachen wie Haskell so einflussreich auf C++?
- Welchen Weg schlägt modernes C++ ein?
- Welche Vorteile besitzt die funktionale Programmierung?
- Was ist funktionale Programmierung?
- Worin zeichnet sich funktionale Programmierung aus?
Fragen über Fragen, die ich natürlich nicht alle in diesem Artikel beantworten kann. Diese Fragen und noch einige Fragen mehr werde ich in den nächsten Artikel beantworten.
Los geht es mit der ersten nicht gestellten Frage. Welche Programmierparadigmen unterstützt C++?
Eine starke Vereinfachung
30 Jahre sind eine sehr lange Zeit in der Softwareentwicklung. Da ist es nicht verwunderlich, dass C++ mit seinen Wurzeln in C mehrere Metamorphosen vollzogen hat.
C startete in den frühen 70er des letzten Jahrhunderts, 1998 wurde der erste C++ Standard C++98 verabschiedet. 13 Jahre später beginnt modernes C++ mit dem zweiten, großen C++ Standard C++11. Viel interessanter als die nackten Zahlen ist aber die Tatsache, dass jeder der drei Schritte auch für die Art und Weise steht, wie Probleme gelöst werden.In C wurden Probleme durch die prozedurale und strukturelle Denkweise gelöst. C++ führte mit der objektorientierten und generischen Programmierung neue Abstraktionen ein. Mit C++11 kam die funktionale Denkweise hinzu.
Bevor ich mich aber fast ausschließlich der funktionalen Programmierung widme, möchte ich die Ideen der objektorientierten, generischen und funktionalen Programmierung kurz skizzieren.
Objektorientierten Programmierung
Objektorientierte Programmierung basiert auf den drei Konzepten Kapselung, Vererbung und Polymorphie.
- Kapselung
-
Ein Objekt kapselt seine Attribute und Methoden und bietet sie durch ein Interface der Außenwelt an. Für diese Eigenschaft, dass sich Instanzen einer Klasse ihre Internas verbergen, hat sich auch der Name data hiding etabliert.
- Vererbung
-
Eine abgeleitete Klasse erbt alle Eigenschaften ihrer Basisklasse. Eine Instanz einer abgeleiteten Klasse kann auch als eine Instanz seiner Basisklasse verwendet werden. Da eine Klasse automatisch alle Eigenschaft ihrer Basisklasse erbt, wird in dem Zusammenhang gerne der Name code reuse verwendet.
- Polymorphie
-
Objekte einer Instanz verändern zu Laufzeit ihr Verhalten. Polymorphie kommt aus dem altgriechischen und steht für Vielgestaltigkeit.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class HumanBeing{ public: HumanBeing(std::stringn):name(n){} virtual std::string getName() const{ return name; } private: std::string name; }; class Man: public HumanBeing{}; class Woman: public HumanBeing{}; |
In dem konkreten Beispiel lässt sich der Name des HumanBeing nur über die Methode getName in Zeile 4 ermitteln (Kapselung). Darüber hinaus ist getName als virtual deklariert. Damit können abgeleitete Klassen die Methode und somit das Verhalten ihrer Objekte verändern (Polymorphie). Sowohl Man als auch Woman sind von HumanBeing abgeleitet.
Generische Programmierung
Die zentrale Idee der generischen Programmierung oder auch der Programmierung mit Templates ist es, Familien von Funktionen und Klassen zu definieren. Durch die Verwendung eines konkreten Typs erzeugt der Compiler automatisch eine Funktion oder auch eine Klasse für diesen Typ. Generische Programmierung erlaubt ähnlich Abstraktionen wie die objektorientierte Programmierung. Ein großer Unterschied besteht darin, dass die Polymorphie bei der objektorientierten Programmierung zur Laufzeit, bei der generischen Programmierung zur Compilezeit stattfindet. Das ist der Grund, dass die Polymorphie zur Laufzeit als dynamische Polymorphie, die Polymorphie zur Compilezeit als statische Polymorphie bezeichnet wird.
Mit dem Funktions-Template lassen sich Objekte tauschen.
1 2 3 4 5 6 7 8 9 10 11 12 |
template <typename T> void xchg(T& x, T& y){ T t= x; x= y; y= t; }; int i= 10; int j= 20; Man huber; Man maier; xchg(i,j); xchg(huber,maier); |
Für das Funktions-Template ist es unerheblich, ob es Zahlen oder Männer vertauschen soll (Zeile 11 und 12). Selbst der Typ-Parameter T (Zeile 1) muss nicht spezifiziert werden, da der Compiler diesen aus den Funktionsargumenten (Zeile 11 und 12) ableiten kann.
Dies gilt nicht für das Klassen-Template. In diesem Fall muss sowohl der Typ-Parameter T als auch der Nichttyp-Parameter N (Zeile 1) angegeben werden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
template <typename T, int N> class Array{ public: int getSize() const{ return N; } private: T elem[N]; }; Array<double,10> doubleArray; std::cout << doubleArray.getSize() << std::endl; Array<Man,5> manArray; std::cout << manArray.getSize() << std::endl; |
Die Anwendung des Klassen-Template Array ist wieder unabhängig davon, ob doubles oder Männer zum Einsatz kommen.
Funktionale Programmierung
Bei den Konzepten der funktionalen Programmierung halte ich mich noch bewusst bedeckt in diesem Artikel. Denn die Konzepte der funktionalen Programmierung lassen sich nicht in einer kleinen Randbemerkung erklären. Nur soviel. In dem kleinen Codeschnipsel kommen die Pendants zu den typischen Funktionen der funktionalen Programmierung map, filter und reduce zum Einsatz. Dies sind die Funktionen std::transform, std::remove_if und std::accumulate in C++.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
std::vector<int> vec{1,2,3,4,5,6,7,8,9}; std::vector<std::string> str{"Programming","in","a","functional","style."}; std::transform(vec.begin(),vec.end(),vec.begin(), [](int i){ return i*i; }); // {1,4,9,16,25,36,49,64,81} auto it= std::remove_if(vec.begin(),vec.end(), [](int i){ return ((i < 3) or (i > 8)) }); // {3,4,5,6,7,8} auto it2= std::remove_if(str.begin(),str.end(), [](string s){ return (std::lower(s[0])); }); // "Programming" std::accumulate(vec.begin(),vec.end(),[](int a,int b){return a*b;}); // 362880 std::accumulate(str.begin(),str.end(), [](std::string a,std::string b){return a + ":"+ b;}); // "Programming:in:a:functional:style." |
In dem kleinen Codebeispiel wende ich noch zwei mächtige Feature aus der funktionalen Programmierung an. Diese sind mittlerweile zum Mainstream in modernem C++ geworden. Automatische Typableitung mit auto und Lambda-Funktionen.
Wie geht's weiter?
Welche funktionalen Feature bietet C++ bereits? Welche Feature kommen mit C++17 und C++20 hinzu? Genau diesen beiden Fragen werde ich im nächsten Artikel beantworten.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...