Surprisingly, C++0x feels like a new language: The pieces just fit together better
than they used to and I find a higher-level style of programming more natural than before
and as efficient as ever. (Bjarne Stroustrup)
C++0x bricht nicht mit C++ Code.
Historie
- Zeitachse C++:
- ARM C++
- Annotated C++ Reference Manual (ARM)
- erster C++ Standard
- Grundlage für C++98
- Bjarne Stroustrup definiert C++ Funktionsumfang
- C++98 (ISO/IEC 14882)
- aktuelle ISO C++ Standard
- C++03
- technische Korrektur des Standards
- TR1
- Technical Report 1
- Ergänzungen der C++ Standard Library
Ziele von C++0x
Den
Prinzipien von C++
- C++ ist eine Multi-Paradigmen Programmiersprache.
- Vertraut dem Programmierer,
- Zahle nicht für das, was du nicht benutzt.
- Brich keinen funktionierenden Code.
- Kontrolle zur Kompilezeit ist besser als zu Laufzeit.
bleibt C++0x treu und legt insbesondere Wert auf
Prinzipien von C++0x
- Bessere Systemprogrammierung ermöglichen.
- Bessere Sprache für die Implementierung von Bibliotheken.
- Soll leichter zu lehren und zu lernen sein.
Kernsprache
Mächtigere Klassendefinition
Ein paar neue Konstrukte:
class NonCopyableClass{
NonCopyableClass & operator=(const NonCopyableClass&) = delete;
NonCopyableClass(const NonCopyableClass&) = delete;
NonCopyableClass() = default;
};
class BaseClass{
virtual void foo();
void bar();
};
class DerivedClass [[base_check]]: public BaseClass{
void foo [[override]] ();
void foo [[override]](int)
void bar [[hiding]] ();
void bar [[hiding]](int);
};
class DelegateConstructor {
int value;
public:
DelegateConstructor(int v) : value(v) {}
DelegateConstructor() : DelegateConstructor(17) { }
};
class BaseClass{
public:
BaseClass(int i);
};
class DerivedClass : public BaseClass{
public:
using BaseClass::BaseClass;
};
template<typename T> class MyVector {
public:
MyVector(std::initializer_list<T> list); // initializer-list constructor
MyVector(const MyVector&); // copy constructor
MyVector(MyVector&&); // move constructor
MyVector& operator=(const MyVector&); // copy assignment
MyVector& operator=(MyVector&&); // move assignment
};
defaulted und deleted Standardmethoden
- die Klasse NonCopyableClass ist nicht kopierbar
- delete: unterdrückt sowohl den automatisch erzeugten Kopier- als auch den Zuweisungsoperator
- default: ein Defaultkonstruktor wird erzeugt, für dessen Implementierung der Kompiler sorgt
- implementiert der Anwender in einer Klasse ein Konstruktor, wird in C++ der Defaultkonstruktor nicht erzeugt der Anwendert muß diesen selber schreiben
void *operator new(std::size_t) = delete;
führt in einer Klasse dazu, das diese nicht mehr dynamisch mittels new erzeugt werden kann
override und hiding von Methoden
- durch das Attribut base_check der Klasse DerivedClass muß das Überschreiben oder Verstecken von Basisklassenmethoden explizit ausgedrückt werden
- override: diese Methode will eine Methode überschreiben
- hidding: diese Methode will eine Methode verstecken (
void bar()
versteckt die gleichnamige Methode der Basisklasse)
- sowohl
void foo [[override]](int)
als auch void bar [[hidding]](int)
sind Syntaxfehler
Delegation von Konstruktoren
- gemeinsame Funktionalität aller Konstruktoren wurde in C++ in einer
init()
Methode angeboten, die alle Konstruktoren aufzurufen hatten
- die Konstruktoren der Klasse DelegateConstructor rufen explizit oder implizit den Konstruktor
DelegateConstructor(int v)
auf, der darfür sorgt, das value auf 17 initialisiert wird
Vererbung von Konstruktoren
using BaseClass::BaseClass
: alle Konstruktoren der Basisklasse in der abgeleiteten Klasse stehen zur Verfügung
Initialiser List Konstruktor
MyVector(std::initializer_list list)
ist ein neuer Konstruktor, der über eine Sequenz von homogenen Wert initialisiert werden kann: MyVector myIntVec={1,2,3,4,5}
- jeder Standardcontainer (vector, list, deque, map, multimap, set, multiset) besitzen diesen Konstruktor, so dass sich diese direkt mit Aggregaten befüllen lassen
- eigene Datentypen oder Funktionen können auch direkt mittels der Sequenz
(std::initializer_list list)
mit Aggregaten umgehen:
double sumAll(std::initializer_list list)
Move Semantic
- Ziel der Move Semantic ist es unnötiges Kopieren zu vermeiden
MyVector(MyVector&&);
und MyVector& operator=(MyVector&&)
werden move constructor und move assignment genannt
- syntaktisch unterscheiden sich diese vom copy constructor und copy assignment durch zwei && (rvalue Referenzen)
- sie statten die Daten mit einer Move Semantic aus, den beim Erzeugen von neuen aus alten Objekten werden die Inhalte transferiert und nicht kopiert
- Beispiel: Copy Semantic versus Move Semantic
std::cout << "move semantic for vector: " << std::endl;
std::vector <int> vec1={1,2,3,4,5,6,7,8,9};
std::vector <int> vec2;
std::cout << "vec1.size(): " << vec1.size() << " vec2.size(): " << vec2.size() << std::endl;
vec2= std::move(vec1);
std::cout << "vec1.size(): " << vec1.size() << " vec2.size(): " << vec2.size() << "\n\n";
std::cout <<"copy semantiv for vector: " << std::endl;
std::vector <int> vec3;
std::cout << "vec2.size(): " << vec2.size() << " vec3.size(): " << vec3.size() << std::endl;
vec3= vec2;
std::cout << "vec2.size(): " << vec2.size() << " vec3.size(): " << vec3.size() << std::endl;
Die Move Semantic stellt bei Container ein nicht zu unterschätzendes Optimierungspotential dar. Sowohl die Initializer List als auch die Move Semantic Konstruktoren bietet jeder Standardcontainer in C++0x an.
Einfacheres Arbeiten mit der Standard Library
Der hochaktuelle gcc 4.5 kann bis auf die range basierte for loop
for (auto itr : myInt) { std::cout << *itr << " ";}
das folgende Codeschnipsel übersetzten
std::vector <int> myInt(10);
std::cout << "vector of 10 times 0: ";
for (auto itr = myInt.begin(); itr != myInt.end(); ++itr){
std::cout << *itr << " ";
}
std::cout << "\n\n";
// for (auto itr : myInt) { std::cout << *itr << " ";}
// for (std::vector<int>::const_iterator itr= intInt.begin(); itr != myInt.end(); ++itr){... }
std::iota(myInt.begin(), myInt.end(),1);
std::cout << "Increment each value by 1: ";
for (auto itr = myInt.begin(); itr != myInt.end(); ++itr){
std::cout << *itr << " ";
}
std::cout << "\n\n";
std::sort(myInt.begin(),myInt.end(),[](int a, int b){ return a >= b;} );
std::cout << "Sort the vector of natural numbers: ";
for (auto itr = myInt.begin(); itr != myInt.end(); ++itr){
std::cout << *itr << " ";
}
std::cout << "\n\n";
std::cout<< "std::min({1,2,4,0,1,-5,2}): " << std::min({1,2,4,0,1,-5,2}) << "\n\n";
std::map<std::string,std::string> phonebook =
{ {"Bjarne","+1 (012) 555-1212"},
{"Herb", "+1 (345) 555-3434"},
{"Alexandrei","+1 (678) 555-5656"} };
std::cout << "Get each telephon number: \n";
for (auto itr = phonebook.begin(); itr!= phonebook.end(); ++itr){
std::cout << itr->first << ": " << itr->second << "\n;
}
- und die Ausgabe
vector of 10 times 0: 0 0 0 0 0 0 0 0 0 0
Increment each value by 1: 1 2 3 4 5 6 7 8 9 10
Sort the vector of natural numbers: 10 9 8 7 6 5 4 3 2 1
std::min({1,2,4,0,1,-5,2}): -5
Get each telephon number:
Alexandrei: +1 (678) 555-5656
Bjarne: +1 (012) 555-1212
Herb: +1 (345) 555-3434
Initialiser Listen
- {}-Initialiser bieten einheitliche Initialisierung von Objekten an und können für alle Initialisierungen verwendet werden
- Beispiele:
- Funktionsaufruf:
std::min({1,2,4,0,1,-5,2})
- std::map:
std::map<std::string,std::string> phonebook = { {"Bjarne","+1 (012) 555-1212"} }
- std::vector:
std::vector vec1={1,2,3,4,5,6,7,8,9}
Base x = Base{1,2};
Base* p = new Base{1,2};
struct Derived : Base {
Derived(int x, int y) :Base{x,y} {};
};
func({a}); // function invocation
return {a}; // function return value
Neue Algorithmen
std::iota(myInt.begin(), myInt.end(),1)
inkrementiert die Elemente des Vektors sukzessive um 1
std::min({1,2,4,0,1,-5,2},[](int a,int b){ return abs(a) >= abs(b)})
kann eine Vergleichsfunktion übergeben werden
- neben Funktionspointern und Funktionsobjekten (Funktor) kann die Vergleichsfunktion direkt definiert werden (Lambda Funktion)
- die restlichen neuen Algorithmen: http://www2.research.att.com/~bs/C++0xFAQ.html#algorithms
Lambda Funktionen
- Aufbau:
[Bindung](Argumente){Funktionskörper}
[]
: Bindung an den lokalen Scope (Lambda Funktionen sind Closures)
[]
: keine Bindung
[=]
: Werte werden kopiert
[&]
: Werte werden referenziert
()
: Argumente (optional) der Lambda Funktion
{}
: Funktionskörper,
- ein einzelner Ausdruck ist gleichzeitig der Returnwert
- kann auch Anweisungen enthalten return Anweisung notwendig
- die Details können unter http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=25 nachgelesen werden
- Beispiele:
std::map< const char , std::tr1::function<double(double,double)> > dispTable;
dispTable.insert( std::make_pair('+',[](double a, double b){ return a + b;}));
dispTable.insert( std::make_pair('-',[](double a, double b){ return a - b;}));
dispTable.insert( std::make_pair('*',[](double a, double b){ return a * b;}));
dispTable.insert( std::make_pair('/',[](double a, double b){ return a / b;}));
std::cout << "3+4= " << dispTable['+'](3,4) << std::endl;
std::cout << "3-4= " << dispTable['-'](3,4) << std::endl;
std::cout << "3*4= " << dispTable['*'](3,4) << std::endl;
std::cout << "3/4= " << dispTable['/'](3,4) << std::endl;
[]{
std::cout << "inline lambda";
std::cout << " function.\n";
}();
auto myAdd= [](int a, int b){return a + b;};
std::cout << "myAdd(1,2): " << myAdd(1,2) << std::endl;
Type Inference
auto
auto
erlaubt es, den Typ aus einem Initialiser abzuleiten
- damit können Schleifen deutlich kompaker geschrieben werden
std::vector <int> myInt{1,2,3,4,5};
for (auto itr = myInt.begin(); itr != myInt.end(); ++itr){
std::cout << *itr << " ";
}
for (auto itr : myInt) {
std::cout << *itr << " ";
}
decltype
decltype
erlaubt es, den Typ aus einem Ausdruck abzuleiten
- damit lässt sich der Typ von komplexen Berechnungen ohne Template Metaprogramming Magic bestimmen
std::vector <int > myVecInt={1};
std::vector <double> myDoubleInt={1.1};
decltype(myVecInt[0]*myDoubleInt[0]) myDouble = 2.1;
void foo(const vector<int>& a, vector<float>& b){
typedef decltype(a[0]*b[0]) ErgTyp;
for (int i=0; i<b.size(); ++i) {
ErgTyp* p = new ErgTyp(a[i]*b[i]);
...
- myDouble besitzt letztendlich den Typ, den es verspricht
typedef decltype(a[0]*b[0]) ErgTyp
erklärt einen neuen Typ, der sich aus der Multiplikation ergibt float
Generische Programmierung
Variadic Templates
- Templates, die eine beliebige Anzahl von Elementen annehmen können
- das folgende Programm stellt zwei Variadic Templates vor
- die Templatefunktion f kann mit einer beliebigen Zahl von Argumenten umgehen
- die Templatefunktion printCommaSeparatedList, die in zwei Ausprägungen vorliegt, gibt eine beliebige Anzahl von Elementen aus
#include <iostream>
#include <string>
#include <vector>
template <typename... Args>
int f(Args... args){
return (sizeof... args);
}
template<typename T>
void printCommaSeparatedList(T value)
{
std::cout<<value<<std::endl;
}
template<typename First,typename ... Rest>
void printCommaSeparatedList(First first,Rest ... rest)
{
std::cout<<first<<",";
printCommaSeparatedList(rest...);
}
int main() {
std::cout << "\n";
auto intList= {1,2,3};
std::vector <int> intVec= {1,2,3,4,5};
std::cout << "f() has " << f() << " arguments\n";
std::cout << "f(42, 3.14) has " << f(42, 3.14) << " arguments\n";
std::cout << "f(\"one\",\"two\",\"three\",\"four\") has " << f("one","two","three","four" )
<< " arguments\n";
std::cout << "f(intVec,intList) has " << f(intVec,intList) << " arguments\n\n";
printCommaSeparatedList("Only primary template used.");
printCommaSeparatedList(42,"hello",2.3,'a');
std::cout << "\n";
}
- erzeugt die Ausgabe:
f() has 0 arguments
f(42, 3.14) has 2 arguments
f("one","two","three","four") has 4 arguments
f(intVec,intList) has 2 arguments
Only primary template used.
42,hello,2.3,a
- Wie funktioniert nun das ganze?
- das entscheidende sind die drei Punkte, die entweder links
template <typename... Args>
oder rechts int f(Args... args){
vom Parameter Args
stehen
- links von Parameter packt der Ellipsen-Operator... das sogenannte parameter pack, rechts entpackt er es wieder
- eine Besonderheit ist der neue Operator
sizeof...
, der direkt mit parameter packs umgehen kann
- f nimmt alle Argumente an und reicht sie an den neuen Operator
sizeof...
durch
- printCommaSeparatedList, besteht besteht aus dem Template, das ein Argument verlangt und dem, das mehr als ein Argument erwartet
- beim Aufruf mit einem Argument
printCommaSeparatedList("Only primary template used.")
wird das primäre Template verwendet und der String ausgegeben
- der Aufruf
printCommaSeparatedList(42,"hello",2.3,'a')
stösst das Template mit mehreren Argument an, gibt das erste Argument mit der Anweisung std::cout<<first<<",";
aus, und ruft sich rekursiv mit der Anweisung printCommaSeparatedList(rest...);
auf
- diese Rekursion terminiert genau dann, wenn
rest
nur noch ein Element enthält, denn jetzt wird das primäre Template verwendet
Pattern Matching und rekursiver Aufrufe sind elementare Bausteine der funktionalen Programmierung, die hier zur Anwendung kommen. Ein interessanteres Beispiel für Variadic Templates ist die
printfFunktion. Im Gegensatz zu ihrem C Pendant ist sie typsicher.
Konstante Ausdrücke
- Template Metaprogramming zeichnet aus, das der Code zur Compilerzeit ausgeführt wird und somit zu Laufzeit als Konstante zur Verfügung steht
- hierzu ist es notwendig, das die Ausdrücke zur Kompilezeit evaluiert werden können neues Schlüsselwort
constexpr
- durch
constexpr
können einfache Funktionen, die einen Returnwert besitzen oder auch Objekte als konstante Ausdrücke deklariert werden
- Beispiel:
constexpr int square(int x) { return x * x; }
int values[square(7)];
- erst durch
constexpr
ist der Aufruf square(7)
möglich
Zusicherungen zur Kompilezeit
static assert
erlaubt es konstante Ausdrücke ohne Template Metaprogramming Magic zur Kompilezeit zu evaluieren
- damit können Voraussetzungen an die Templateparameter geprüft werden und gegebenfalls lesbare Fehlermeldungen ausgegeben werden
static_assert(sizeof(int) == 4, "This code only works for sizeof(int) == 4")
stellt sicher, das int die Länge 4 besitzt
Weitere Features
Standard Library
Thread
- C++0x besitzt eine einheitliche Threading Schnittstelle
- die Thread Schnittstelle ist eines der letzten Featues in C++0x
Memory Modell
Grundproblem
Schreibt ein Thread eine gemeinsame Variable während ein andere diese liest, ist das Verhalten nicht deterministisch.
x=y=0
Thread 1 Thread 2
x=1 y=1
r1=y r2=x
Welchen Wert hat r1 und r2 am Ende des Programms? Können beide Werte 0 sein?
sowohl Optimierung auf Hardware Ebene (Schreibepuffer) wie auch Standard Compiler Transformationen machen dies möglich (Bruch der
sequentiellen Consistenz)
Lösung:
- Locks
Thread 1 Thread 2
lock(l) lock(l)
x= 1; y=1
r1=y r2=x
unlock(l) unlock(l)
- Atomics
atomic x=y=0
Thread 1 Thread 2
x=1 y=1
r1=y r2=x
- durch atomic von x und y werden die Schreibaktionen atomar und werden sofort zwischen den Threads sichtbar
- atomare Datentypen entsprechen im Standardfall von C++0x den volatilen Datentypen in Java
- die C++ Datentypen volatile haben nichts mit Threading zu tun
- das C++0x ist an das Javas Memory Modell angelehnt
- atomare Datentypen erlauben lockfreies Programmieren
Ein Memory Modell besteht aus:
- atomaren Operationen ohne sie ist Synchronisation nahezu unmöglich
- partielle Ordnung von Operationen Reihenfolge von Operationen, die der Compiler nicht verändern darf (_happens before_)
- Speichersichbarkeit Zeitpunkt, ab dem der Speicher für alle Threads den gleichen Wert besitzt
- Data Race Semantik Grundlage für Optimierungen des Compilers, sodass der Code seine Bedeutung behält
Das
Double Checked Locking Pattern ist eine optimierte Spezialform des
SingletonPattern in Multithreaded Umgebungen. Es ist aber ohne eine Memory Modell nicht Threadsicher.
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
Lebenszeit
std::thread(fun)
startet einen Thread, wobei func eine Funktionspointer oder ein Funktor ist
- func kann belieg viele Argumente annehmen CppNext#Variadic_Templates
class Work{
public:
void operator()(int i,std::string s,std::vector<double> v);
};
std::thread t(Work(),42,"hello",std::vector<double>(23,3.141));
join
lässt den Vaterthead auf die Beendigung des Threads warten, detach
löst die Lebenszeit des Threads vom Vater
Schutz von Daten
- es gibt verschiedene Muteces
- nicht rekursiv (
std::mutex
)
- rekursiv (
std::recursive_mutex
)
- nicht-rekursiv mit Zeitvorgaben (
std::timed_mutex
)
- rekursiv mit Zeitvorgaben (
std::recursive_timed_mutex
)
- Muteces sollten nicht direkt verwendet, sondern in
std::unique_lock<>
oder std::lock_guard<>
gekapselt werden
- Beispiel: einfacher Lock
std::mutex m;
my_class data;
void foo(){
std::lock_guard<std::mutex> lk(m);
process(data);
}
- versuche das Lock 3 Millisekunden lang zu bekommen
std::timed_mutex m;
my_class data;
void foo(){
std::unique_lock<std::timed_mutex> lk(m,std::chrono::milliseconds(3)); // wait up to 3ms
if(lk) process(data);
}
- verzöge das Locken der Ressoure und Locke alle Ressourcen gleichzeitig
struct X{
std::mutex m;
int a;
std::string b;
};
void foo(X& a,X& b){
std::unique_lock<std::mutex> lock_a(a.m,std::defer_lock);
std::unique_lock<std::mutex> lock_b(b.m,std::defer_lock);
std::lock(lock_a,lock_b);
// process a and b
}
- Vorteile
- kapseln des Locks in
std::lock_guard
bzw. std::unique_lock
Lock wird automatisch wieder freigegeben, sobald die Lock-Wächter out of scope gehen; Anwendung des RAII Idioms
std::lock
verhindert Deadlocks, die durch Aufrufe der Form foo(x,y) und foo(y,x) resultieren können
beiden Muteces werden atomar gelockt
Initialisierung der Daten
Das threadsicher Initialisieren von Daten ist auf mehrer Arten möglich.
class MyClass{
int i;
public:
constexpr MyClass():i(0){}
MyClass(int i_):i(i_){}
};
MyClass x; // 1
void bar(){
static myClass z(42); // 2
}
MyClass* p=0;
std::once_flag p_flag; // 3
void create_instance(){
p=new MyClass(43);
}
void baz(){
std::call_once(p_flag,create_instance); // 3
}
constexpr
stellt sicher, das (1) zur Compilezeit initialisiert wird (CppNext#Konstante_Ausdrücke)
- statische Variablen in einem Block Scope (2) werden threadsafe initialisiert
- durch
std::once_flag
in Kombination mit std::call_once
wird die Funktion (3) genau nur einmal ausgeführt
Signale zwischen Threads
- durch Bedingungsvariablen (condition variables) können Thread sich über Events synchronisieren
std::mutex m;
std::condition_variable cond;
bool data_ready;
void process_data();
void foo(){
std::unique_lock<std::mutex> lk(m);
cond.wait(lk,[]{return data_ready;}); // (1)
process_data();
}
void set_data_ready(){
std::lock_guard<std::mutex> lk(m);
data_ready=true;
cond.notify_one(); // (2)
}
cond.wait(lk,[]{return data_ready;})
hebt den Lock auf, schaut nach ob data_ready gilt und fährt gegebenfalls mit dem prozessieren der Daten fort
cond.notiy_one()
signalisiert, das die Daten fertig sind
Thread lokale Daten
- durch
thread_local
werden Daten definiert, die
- dem Thread gehören
- ihre Lebenszeit mit dem Thread teilen
- statische Variablen in dem Sinne sind, das sie beim ersten Mal initialisiert werden
std::string foo(std::string const& s2){
std::thread_local std::string s="hello";
s+=s2;
return s;
}
- der String s beim ersten Durchlauf initialisiert und s2 erweitert
- jeder weitere Durchlauf erweitert s um den Eingabeparameter s2
Futures
Starte eine Task(Job in einem neuen Thread), warte aber nicht auf ihren Wert, sondern hole ihn später ab
- Future: der Thread, der den Wert abholt
- Promise: der Thread, der den Wert liefert
std::async
template<class T, class V>
struct Accum { // simple accumulator function object
T* b;
T* e;
V val;
Accum(T* bb, T* ee, const V& v) : b{bb}, e{ee}, val{vv} {}
V operator() () { return std::accumulate(b,e,val); }
};
double compute(std::vector<double>& v){
//spawn many tasks if v is large enough
if (v.size()<10000) return std::accumulate(v.begin(),v.end(),0.0); (1)
auto f0 {std::async(Accum{&v[0],&v[v.size()/4],0.0})};
auto f1 {std::async(Accum{&v[v.size()/4],&v[v.size()/2],0.0})};
auto f2 {std::async(Accum{&v[v.size()/2],&v[v.size()*3/4],0.0})};
auto f3 {std::async(Accum{&v[v.size()*3/4],&v[v.size()],0.0})};
return f0.get()+f1.get()+f2.get()+f3.get();
}
- abhängig von der Grösse des Vektors v werden vier neue Threads gestartet und in diesen die entsprechenden Werte akkumuliert
- die ganze Berechnung wird durch
f0.get()+f1.get()+f2.get()+f3.get()
synchronisiert (geblockt), den nun werden die Ergebnisse eingesammelt
- zum jetztigen Zeitpunkt ist noch nicht klar, ob
std::async
in den aktuellen C++0x Standard aufgenommen wird
std::package_task
verpackt eine Funktion(Funktionsobjekt) auf ähnliche Weise in einer Task
std::promise
- setze den Rückgabewert der Task
set_value
oder set_exception
void asyncFun(std::promise<int> intPromise){
int result;
try {
// calculate the result
intPromise.set_value(result);
} catch (MyException e) {
intPromise.set_exception(std::copy_exception(e));
}
}
std::future
- starte die Task (2), verbinde dich mit dem Promise durch
get_future
(1) und hole den Wert mit get
(3) ab
std::promise<int> intPromise;
std::unique_future<int> intFuture = intPromise.get_future(); (1)
std::thread t(asyncFun, std::move(intPromise)); (2)
// do some other stuff
int result = intFuture.get(); // may throw MyException (3)
unique_future
besitzt im Gegensatz zu shared_future
Move Semantik
Unter
http://www.stdthread.co.uk/doc/headers.htmlist die Threading API schön beschrieben.
Smart Pointer.
Smart Pointer sind spezielle, intelligente Zeiger, die eine gewrappte Ressource mit zusätzlicher Funktionalität ausstatten.
Die drei neuen Smart Pointer
shared_ptr
,
weak_ptr
und
unique_ptr
lösen die bekannten
auto_ptr
aus C++ ab. Sie überwachen den Lebenszyklus der Ressource nach dem
RAII Idiom. Jeder dieser Smart Pointer besitzt sein spezielles Einsatzgebiet. Sowohl
shared_ptr
als auch insbesondere
unique_ptr
besitzen ein ähnliches Interface wie der
auto_ptr
. Der
auto_ptr
, der genau eine Ressource kapselt, wird als deprecated erklärt, da er zwei grosse Schwächen besitzt:
- beim Kopieren eines
auto_ptr
wird deren Inhalt mitkopiert Transfer of Ownership oder Move Semantik
- er ist weder kopier- oder zuweisbar kann nicht in Standardcontainern verwendet werden
std::auto_ptr<int> auto1(new int(5));
std::auto_ptr<int> auto2(auto1);
int a= *auto1;
- auto_ptr:
- der Aufruf
int a= *auto1
ist undefiniert
Beispiel
Das Programm
std::vector< std::tr1::shared_ptr<std::string> > sharedVec;
std::tr1::shared_ptr<std::string> sharedPtr( new std::string("initial"));
sharedVec.push_back(std::tr1::shared_ptr<std::string>( sharedPtr ));
std::cout << "Values: sharedPtr sharedVec" << std::endl;
std::cout << "Initial: " << " " << *sharedPtr << " " << *sharedVec[0]
<< std::endl;
*sharedPtr="modfied";
std::cout << "Modified: " << " " << *sharedPtr << " " << *sharedVec[0]
<< std::endl;
std::cout << "use_count: " << sharedPtr.use_count() << std::endl;
{
std::tr1::shared_ptr<std::string> localSharedPtr( sharedPtr );
std::cout << "use_count: " << sharedPtr.use_count() << std::endl;
}
std::cout << "use_count: " << sharedPtr.use_count() << std::endl;
std::tr1::weak_ptr<std::string> weakPtr( sharedPtr );
std::cout << "use_count: " << sharedPtr.use_count() << std::endl;
std::unique_ptr<std::string> uniquePtrFirst( new std::string("only one") );
// std::unique_ptr<std::string> uniquePtrSecond( uniquePtrFirst); will not compile
std::unique_ptr<std::string> uniquePtrSecond( std::move(uniquePtrFirst));
std::cout << "uniquePtrFirst.get(): " << uniquePtrFirst.get() << " *uniquePtrSecond.get(): "
<< *uniquePtrSecond.get() << std::endl;
liefert die folgende Ausgabe:
Values: sharedPtr sharedVec
Initial: initial initial
Modified: modfied modfied
use_count: 2
use_count: 3
use_count: 2
use_count: 2
uniquePtrFirst.get(): 0 *uniquePtrSecond.get(): only one
shared_ptr
- shared_ptr:
Mehrere shared_ptr
können auf eine Ressource verweisen.
- Referenzzähler
- inkrementiert, falls eine neuer
shared_ptr
auf die Ressource erzeugt wird
- dekrementiert, falls ein
shared_ptr
gelöscht wird (out of scope)
- die Ressource wird genau dann gelöscht, wenn der Referenzähler auf 0
deterministisches Löschen der Ressource != Garbage Collection, den hier wird die Ressource nur freigegeben
- Destruktor:
- dem Konstruktor kann eine Funktion oder Funktor mitgegeben werden um die Ressource freizugeben
- für den
shared_ptr
shared_ptr(Y * p, D d);
wird im Destruktor
d(p)
aufgerufen
- ist kopier- und zuweisbar kann in Standard Containern verwendet werden
- Schwäche:
- zyklische Abhängigkeiten erlauben es nicht die Ressource freizugeben
weak_ptr
Bricht zyklische Refenzen auf.
- bietet nur ein sehr einfaches Interface an
- modifiziert nicht den Referenzzähler
- kann selber keine Ressource besitzen
- ist nicht wirklich ein Smart Pointer
- erlaubt im Gegensatz zum
shared_ptr
und weak_ptr
kein transparenten Zugriff auf die Ressource
- um auf die Ressource eines
weak_ptr
zuzugreifen, wird diese gelockt und ein shared_ptr
damit initialisiert
std::weak_ptr<X> resource;
if(shared_ptr<X> px = resource.lock()){
// use *px to access the resource
}
else{
// resource has expired
}
unique_ptr
Besitz exclusive eine Ressource.
- unique_ptr:
unique_ptr
ist nahezu Interfacecompatile mit dem auto_ptr
Smart Pointern, unterbindet aber dessen fehlerträchtiges kopieren
- entsprechend zum
auto_ptr
besitzt der unique_ptr
exclusive seine Ressource, die beim Kopieren transferiert wird Move Semantik
unique_ptr
lassen sich aber nicht direkt kopieren, sondern müssen die Funktion std::move
verwenden
auto_ptr<int> ap1(new int);
auto_ptr<int> ap2 = ap1; // OK, albeit unsafe
unique_ptr<int> up1(new int);
unique_ptr<int> up2 = up1; // compilation error: private copy constructor inaccessible
- kann in Algorithmen verwendet werden, in den nur mit Rvalue Referenzen (temporäre Objekte; Objekte ohne Namen) oder expliziten Movekonstruktoren gearbeitet wird
unique_ptr<int> up(new int)
unique_ptr<int> up1= std::move(up);
- erreicht wird dies Verhalten durch einen privaten Kopierkonstruktor und einen öffentlichen Movekonstruktor
template <class T>
class unique_ptr{
public:
unique_ptr(unique_ptr&& u); // rvalues bind here
private:
unique_ptr(const unique_ptr&); // lvalues bind here
};
Container
Tuple
Der neue Container Typ
std::tuple
ist eine Verallgemeinerung des bekannten C++ Datentyps
std::pair
, der Paare verschiedenes Typs binden kann.
Tuple haben die folgenden Eigenschaften:
- besitzen eine feste Dimension
- kann mindestens 10 verschiedene Elemente binden
- kann in einem Standard Container verwendet werden
- Tupleinstanzen können genau dann miteinander verglichen werden, wenn sie einerseits die gleiche Länge besitzen und andererseits deren Elemente miteinander vergleichbar sind
std::tr1::tuple<std::string,int,float> tup1("first tuple",3,4.17); (1)
std::tr1::tuple<std::string,int,double> tup2= std::tr1::make_tuple("second tuple",4,1.1);(1)
std::cout << std::boolalpha;
std::cout << std::tr1::get<0>(tup1) << " and " << std::tr1::get<0>(tup2) << std::endl; (2)
std::cout << "tup1 < tup2: " << (tup1 < tup2) << std::endl; (4)
std::tr1::get<0>(tup2)= "Second Tuple"; (3)
std::cout << std::tr1::get<0>(tup1) << " and " << std::tr1::get<0>(tup2) << std::endl; (2)
std::cout << "tup1 < tup2: " << (tup1 < tup2) << std::endl; (4)
- Tuple können sowohl durch einen Kunstruktor als auch durch die Hilfsfunktion
std::tr1::make_tuple
erzeugt werden (1)
- auf einzelne Elemente kann mittels
std::tr1::get<ind>(tup)
lesend (2) und gegebenfalls schreibend (3) zugegriffen werden zugegriffen
- Tuple unterstützen Vergleiche (4)
Die Ausgabe:
first tuple and second tuple
tup1 < tup2: true
first tuple and Second Tuple
tup1 < tup2: false
Array
array
ist ein Standard Template Library (STL) konformer Container Wrapper for Arrays fester Länge.
Das C++0x
std::array
verbindet das Laufzeitverhalten des C-Arrays mit dem Interface des C++
std:vector
.
std::tr1::array <int,8> a1={1,2,3,4,5,6,7,8}; (1)
std::tr1::array <int,8> a2={1,2,3,4,5}; (1)
std::copy( a1.begin(), a1.end(), (2)
std::ostream_iterator< int >(std::cout," "));
std::cout << "\n";
std::copy( a2.begin(), a2.end(), (2)
std::ostream_iterator< int >(std::cout," "));
std::cout << "\n";
a2[7]=8; (3)
std::copy( a2.begin(), a2.end(), (2)
std::ostream_iterator< int >(std::cout," "));
std::cout << "\n";
std::cout << "std::accumulate(a2.begin(),a2.end(),0): " (2)
<< std::accumulate(a2.begin(),a2.end(),0) << "\n";
std::cout << "a2.size(): " << a2.size() << std::endl; (2)
- der Konstruktor von
std::array
verlangt ein Aggregat; fehlende Werte werden Defaultinitialisiert (1)
std::array
kann in den Algorithmen der STL verwendet werden (2)
- als Array erlaubt es natürlich den Indexzugriff (3)
Die Ausgabe:
1 2 3 4 5 6 7 8
1 2 3 4 5 0 0 0
1 2 3 4 5 0 0 8
std::accumulate(a2.begin(),a2.end(),0): 23
a2.size(): 8
Ungeordnete Assoziative Container (Hashtabelle)
Die Ungeordneten Assoziativen Container verhalten sich wie die bekannten Geordneten Assoziativen Container.
Der feine Unterschied ist
- die Schlüssel der ungeordneten Datentypen sind nicht geordnet konstante Zugriffszeit ist möglich
- die geordneten Datentypen haben logarithmische Zugriffszeit
- die Elemente der Ungeordneten Assoziativen Container müssen nicht vergleichbar sein
Die Hashtabelle schafften es nicht mehr in in C++98 Standard
viele Compilerbauer boten ihre eigene Erweiterungen an
Namen wie hash_map waren schon vergeben
- es gibt vier verschiedene Ungeordnete Assoziative Arrays
- unordered_set: eindeutige Schlüssel
- unordered_multiset: mehrere gleiche Schlüssel möglich
- unordered_map: nur ein (Schlüssel,Wert) Paar mit gleichem Schlüssel erlaubt
- unordered_multimap: mehrere (Schlüssel,Wert) Paare mit gleichem Schlüssel möglich
- jedem Ungeordneten Assoziativen Array steht aus C++98 ein Geordnetes Assoziatives Array gegenüber
- die Anwendung der Ungeordneten Assoziativen Arrays ist ziemlich unspektakulär, da dieser der der Geordneten Assoziativen Arrays entspricht
std::map<std::string,int> m { {"Dijkstra",1972},{"Scott",1976},{"Wilkes",1967},{"Hamming",1968} };
m["Ritchie"] = 1983;
for(auto x : m) std::cout << '{' << x.first << ',' << x.second << '}';
std::unordered_map<std::string,int> um { {"Dijkstra",1972},{"Scott",1976},{"Wilkes",1967},{"Hamming",1968} };
um["Ritchie"] = 1983;
for(auto x : um) std::cout << '{' << x.first << ',' << x.second << '}';
Reguläre Ausdrücke
Der Umgang mit regulären Ausdrücken in C++0x lässt sich in drei Schritte zerlegen.
std::regex rgx(R"\d+"); (1)
std::smatch match; (2)
if (std::regex_search(std::string("123A43"), match, rgx)) (3)
std::cout << "match found after " << match.prefix() << '\n';
std::regex rgx(R"\d+")
hält den regulären Ausdruck
R"\d+"
bezeichnet einen Raw String Literal in C++0x
- der String folgt per Default der ECMAScript Grammatik, aber auch POSIX BRE, ERE, awk, grep, egrep oder sed Syntax ist möglich
- neben
std::regex
gibts auch std::wregex
für wide characters wchar_t
std::smatch match
erhält das Ergebnis der Suche
match[0]
: der Gesamtmatch
match[i]
: i>0 sind die Teilmatches
position
, suffix
, prefix
und length
liefern weiter Informationen
std::regex_search
verarbeitet das Suchergebniss weiter
Wiederholtes Suchen
Mit
std::regex_iterator
und
std::regex_token_iterator
lässt sich ein Iterator über eine Eingabesequenz definieren.
std::regex_iterator
: liefert die Zeichen, die dem regulären Ausdruck entsprechen
std::regex_token_iterator
: splittet die Eingabesequenz mit Hilfe der regulären Ausdrücke Tokenizer
std::cout << std::endl;
boost::regex reg1("[^13579,]");
std::string str1="1,2,3,4,5,6,7,8,9,10,11,12";
boost::sregex_iterator it1(str1.begin(),str1.end(),reg1);
boost::sregex_iterator end1;
std::cout << "Character Stream: " << std::endl;
while (it1 != end1) std::cout << " " << *it1++;
std::cout << "\n\n\n";
boost::regex reg2(",");
std::string str2="1,2,3,4,5,6,7,8,9,10,11,12";
boost::sregex_token_iterator it2(str2.begin(),str2.end(),reg2,-1);
boost::sregex_token_iterator end2;
std::cout << "Token Stream: " << std::endl;
while (it2 != end2) std::cout << " " << *it2++;
std::cout << "\n";
std::cout << std::endl << std::endl;
Zufallszahlen
Zufallszahlen sind in vielen Bereichen notwendig:
- Tests
- Spiele
- Simulationen
- Sicherheit
Der Zufallszahlengenerator (
std::variate_generator
) besteht aus zwei Teilen. Einem Generator, der Sequenzen von Zufallszahlen erzeugt und einer Verteilung, die die Zufallszahlen in einem vorgegebenem Bereich verteilt.
std::tr1::mt19937 rng; // generator
std::tr1::uniform_int<> six(1,6); // distribution
std::tr1::variate_generator<std::tr1::mt19937, std::tr1::uniform_int<> > dice(rng, six);
for ( int i=1; i<= 9; ++i){
std::cout << "dice["<< i << "]: " << dice() << std::endl;
}
Ein Überblick über verschiedene Generatoren und Verteilungen ist unter
http://www.boost.org/doc/libs/1_41_0/libs/random/index.html. Dies entspricht annähernd der Funktionalität von C++0x.
Type Traits
Ein Program, das ein anderes Program erzeugt, nennt sich Metaprogramm. Tut sie dies noch zur Compilezeit mit Templates, dann heißt diese Technik
Static Template Metaprogramming. Die
type_traits
Bibliothek erlaubt Typabfragen, Vergleiche und Modifikationen zur Compilezeit. Damit lassen sich Algorithmen speziell auf den Datentyp zuschneiden und doch generisch aufrufen.
#include <tr1/type_traits>
#include <iostream>
template <class Ty>
void multBy3Impl(Ty val, const std::tr1::true_type&){
std::cout << "Arithmetic type: ";
std::cout << 3*val << std::endl;
}
template <class Ty>
void multBy3Impl(Ty val, const std::tr1::false_type&){
std::cout << "No arithmetic type: ";
std::cout << val << val << val << std::endl;
}
template <class Ty>
void multBy3(Ty val){
multBy3Impl(val, std::tr1::is_arithmetic<Ty>());
}
int main(){
multBy3(1);
multBy3(5.5);
multBy3(std::string("Only for testing purpose. "));
}
Typ Abfragen
- Primary Type Categories (
is_void
, is_floating_point
, is_array
... )
- Composite Type Categories (
is_arithmetic
, is_object
, is_member_pointer
... )
- Type Properties (
is_const
, is_pod
, is_abstract
,... )
Typ Vergleiche
- Type Relationships (
is_same
, is_convertible
, is_base_of
... )
Type Modfikationen
- Typ Transformations (
remove_const
, add_const
, remove_reference
... )
- Alignment (
alignment_of
, aligned_storage
)
Der ganze Rest an Funktionen kann unter
http://msdn.microsoft.com/en-us/library/bb982077.aspxnachgelesen werden.
Optimiertes Kopieren von Datenstrukturen
Ein generisches Kopieralgorithmus für Standardcontainer kann so aussehen:
template<typename I1, typename I2, bool b
I2 copy_imp(I1 first, I1 last, I2 out, const std::tr1::integral_constant<bool, b>&){
while(first != last){
*out = *first;
++out;
++first;
}
return out;
}
Jedes Element des Bereiches
[first,last[
wird sukzessive an den Bereich
[out,...[
kopiert.
Dies geht mit
memcpy
deutlich schneller.
template<typename T>
T* copy_imp(const T* first, const T* last, T* out, const std::tr1::true_type&){
memcpy(out, first, (last-first)*sizeof(T));
return out+(last-first);
}
Die Datenstrukturen müssen aber hinreichend einfach sein um
memcpy
zu verwenden:
- die Iteratoren müssen Pointer sein (
const T* first, const T* last, T* out
)
- die Iteratoren müssen auf die gleichen Typen verweisen (
<typename T>
enthält nur ein Typ)
- die Elemente des Containers müssen einen trivialen, vom Compiler automatisch erzeugten, Zuweisungsoperator besitzen (
const std::tr1::true_type&
)
Ein Aufruf der Form
template<typename I1, typename I2>
I2 copy(I1 first, I1 last, I2 out){
typedef typename std::iterator_traits<I1>::value_type value_type;
return copy_imp(first, last, out, std::tr1::has_trivial_assign<value_type>());
}
- stösst die richtige copy_imp Implementierung an
- durch
typedef typename std::iterator_traits<I1>::value_type value_type
wird der Typ der Containerelemente bestimmt um ihn anschliessend in std::tr1::has_trivial_assign<value_type>
zu nutzen
- abhängig vom Ergebniss der Evaluierung wird der optimierte oder generische Kopieralgorithmus verwendet
- das Ergebnis des Aufrufs
std::tr1::has_trivial_assign<value_type>
ist eine Klasse, so dass der Dispatch auf das vierte Argument der copy_imp Methoden vollzogen wird
- der Ausdruck
typedef integral_constant<bool, true> true_type;
definiert eine Spezialisierung true_type
von std::tr1::integral_constant<bool, b>
Referenz Wrapper
Referenzen sind in C++ keine
First class objects.
sie können nicht kopiert werden
sie können nicht in Standardcontainern verwendet werden, da sie hierzu
copy-constructible and assignable sein müssen
dieser Ausdruck lässt sich in C++ nicht kompilieren:
std::vector<int&>
Die C++0x Bibliothek
reference_wrapper
erzeugt aus Objekten vom Type T&
copy-constructible and assignableObjekte. Damit lassen sich
- Objekte in Containern verwenden, die sich nicht Kopierkonstruierbar und Zuweisbar sind (Dateien, Locks oder auch Netzwerkverbindungen)
- Objekte mit Referenzsemantik in Containern verwenden
reference_wrapper ist ein copy-constructible and assignable Wrapper um das Objekt vom Typ T&.
std::vector<bool> copyVec;
std::vector< std::tr1::reference_wrapper<bool> > refVec;
bool b=false;
copyVec.push_back(b);
refVec.push_back(std::tr1::ref(b));
std::cout << std::boolalpha;
std::cout << "Values: b copyVec refVec" << std::endl;
std::cout << "Initial: " << b << " " << copyVec[0] << " " << refVec[0] << std::endl;
b= true;
std::cout << "Modified: " << b << " " << copyVec[0] << " " << refVec[0] << std::endl;
refVec[0] ist eine Referenz auf b und wird durch b= true modifiziert
Die Hilfsfunktion
ref<T>
gibt einen T& Wrapper Objekt
cref<T>
gibt einen const T& Wrapper Objekt
zurück.
template <typename T>
void doubleMe(T t){
++t;
};
int f=1;
std::cout << "initial value: " << f << std::endl;
doubleMe(f);
std::cout << "doubleMe(f): " << f << std::endl;
doubleMe(std::tr1::ref(f));
std::cout << "doubleMe(ref(f)) : " << f << std::endl;
bind und function
Die beiden Librarys
bind
und
function
ergänzen sich sehr gut.
bind
- erlaubt Argumente an beliebige Positionen einer Funktion, Methode oder eines Funktionsobjektes zu binden
- das resultierende Funktionsobjekt kann direkt aufgerufen, in den Algorithmen der Standard Template Library verwendet oder in einem Funktionsobjekt
function
gespeichert werden
- für Argumente ohne Wert können Platzhalter eingeführt werden
function
- ermöglicht die einfache Definition von Funktionsobjekten
- diese Funktionsobjekten sind first class functions
- erlauben die einfache Definition von Callbacks
bind
und function
- teilweise evaluieren jedes beliebigen Arguments mit
bind
und binden der neuen Funktion mit function
sehr mächtiges currying lässt sich umsetzen
- Funktionsobjekte, die mit
bind
erzeugt werden, können direkt mit function
gebunden und später aufgerufen werden.
Die Funktionalität von bind kann oft durch lambda Funktionen ausgedrückt, die von function wird durch auto angeboten.
double divMe(double a, double b){
return double(a/b);
}
template <typename T>
T addMe(T a, T b){
return a+b;
};
int main(){
std::cout<< "1/2.0= " << bind(divMe,1,2.0)() << std::endl; (1)
function<double(double)> myDiv= bind(divMe,_1,2.0); (2)
std::cout<< "1/2.0= " << myDiv(1) << std::endl;
function<int(int)> addInt = bind(addMe<int>, _1, 2);
std::cout << "1+2= " << addInt(1) << std::endl;
function<std::string(std::string)> addString = bind(addMe<std::string>,"first",_1); (3)
std::cout << "first + second= " << addString("second") << std::endl;
auto funcObject1= bind(addMe<std::string>, "first",_1);
std::cout << "first + second= " << funcObject1("second") << std::endl;
auto funcObject2= bind(addMe<std::string>, _1, _2);
std::cout << "first + second= " << funcObject2("first","second") << std::endl;
std::vector<int> myVec{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
std::copy_if( myVec.begin(), myVec.end(), (4)
std::ostream_iterator<int>( std::cout, ", " ),
bind( std::logical_and<bool>(),
bind( std::greater <int>(),_1,9 ),
bind( std::less <int>(),_1,16 )));
std::cout<< std::endl;
/* compiles only with gcc 4.5 (5)
std::copy_if( myVec.begin(), myVec.end(),
std::ostream_iterator<int>( std::cout, ", " ),
[](int a){ return (a>9)&&(a<16);});
std::cout << std::endl;
*/
}
bind(divMe,1,2.0)()
definiert einen Funktionskörper, bindet die Argumente 1 und 2.0 und ruft ihn auf
bind(divMe,_1,2.0)
bindet das zweite Argument mit 2.0, führt eine Platzhalter _1 für das erste Argument ein, bindet das Funktionsobjekt an function<double(double)>
und führt es anschliessend mit myDiv(1)
aus
auto funcObject1
erleichert die Definition von Funktionsobjekten
- zuviele geschachtelte bind Ausdrücke (ich habe die ganzen Namespaces der Lesbarkeit wegen bekannt gemacht), können verwirrend wirken
- diese Funktion bietet die gleiche Funktionalität wie (4) an
Ausgabe:
1/2.0= 0.5
1/2.0= 0.5
1+2= 3
first + second= firstsecond
first + second= firstsecond
first + second= firstsecond
10, 11, 12, 13, 14, 15,
Weitere Bibliotheken
- time utilities
- return Wert Evaluierung von Funktionsobjekten mit return_of; die Funktonalität wird schon durch die Alternative Funktionssyntax mit decltype angeboten
- einfache Aufrufwrapper mit
mem_fn
; die Funktionalität wird schon durch std::bind angeboten
Compilerunterstützung
Weitere Informationen
Verarbschiedung des Standards
Mail am 15.03.2010 an comp.lang.c++.moderated von Herb Sutter.
- March 2010 ISO C++ standards meeting
...
1. Approved Final Committee Draft (FCD) for C++0x
The biggest news is that this afternoon we voted in the final
remaining feature changes to C++0x, and to much applause then
unanimously approved the text for international ballot as a Final
Committee Draft (FCD). FCD means that, assuming no surprises, we
intend to do only bug fixes and editorial corrections for the next
year or so, and then ballot a final standard. If we can do that,
assuming all goes well, C++0x could officially be published as soon as
next year as ISO C++ 2011, and we can stop with the "x-is-hex" jokes
and just start calling it C++11.
This is a big milestone, and it was achieved thanks to removing a
couple of controversial features last summer and a whole lot of work
by the ISO C++ committee members over the past six months in
particular. That work includes countless hours spent between our full
face-to-face meetings at face-to-face ad-hoc meetings to swat library
bugs, teleconferences on resolving core language questions, and
triple-
digit person-hours invested in four teleconferences during December-
February purely about C and C++ compatibility that have greatly helped
to identify and attempt to resolve minor areas of divergence between
the C++0x draft standard and the C1x draft standard (as both are now
in progress; C1x is targeting completion and publication in 2012).
All in all, your committee members have put in an enormous amount of
effort to bring this in, and the draft is in far better shape for this
meeting than anyone could have expected last summer. For comparison,
in my and several others'; opinions, it's in better shape than the FCD
of the C++98 standard.
...
Weiterlesen...