Sunteți pe pagina 1din 93

Subiecte avansate C++

Dorin Mancu
dmancu@memiq.ro
Obiective

 Studiul și aplicarea unor elemente mai


complexe ale limbajului C++
 Folosirea C++ în programarea orientată pe
obiecte.
 Programare multithreading - POSIX
Conținut

 Supraîncărcarea operatorilor: () [] -> new delete


 Moștenire, polimorfism
 RTTI - Run-time Type Information
 Gestiunea memoriei, alocare de obiecte mici
 Templates – funcții și clase
 Standard Template Library
 C++11 (opțional)
 Fire de execuție POSIX (alternativă STL thread)
Bibliografie

 The C Programming Language, Brian W.


Kernighan, Dennis M. Ritchie, ed. 2, 1988
 The C++ Programming Language – Bjarne
Stroustrup, ed. 4, 2013 (C++11)
Supraîncărcarea operatorilor
 Supraîncărcare operatori unari și binari, ca funcții
membru sau funcții friend:
 @x  x.operator@() operator@(x)
 x@  x.operator@(int) operator@(x, int)
 x@y  x.operator@(y) operator@(x, y)
 Operator ()
a(i, j) = a(j,i);
 Operator []
Persoana p(“Popescu”, “Ion”);
carteTelefon[p] = “0256 - 2345678”;
cout << carteTelefon[“0256 - 2345678”];
 Operator ->
a->f(); (a.operator ->())->f();
 Operatorul new, delete
void* operator new(size_t sz);
void operator delete(void* p);
 Aplicații:
 implementare Object Pool – new & delete; de
verificat aplicarea constructorilor!
 smart pointer – constructori, destructori, *, ->, =, ==,
reset(), get()
 Ownership transfer pointer (strict ownership)
 Reference counting pointer (referințe circulare!)
 Variante cu template
Moștenire, polimorfism
 Funcții virtuale
 Mecanism polimorfism – late binding
 Implementare polimorfism – vptr, vtab
 Polimorfism în contextul moștenirii multiple
 Polimorfism dublu – acțiune depinde de două
obiecte
Exercițiu: implementare joc piatră – foarfecă - hârtie
Run-time Type Information
 Alternativă la polimorfism
 Problemă dacă clasele fii au funcții ce nu se
întâlnesc la părinți
 RTTI – aplicabil doar obiectelor polimorfice (cel
puțin un membru virtual)
 vtab conține un pointer la un obiect de tip
std::type_info
 Componente:
 operator typeid
 operator dynamic_cast<>
 clasa type_info
Clasa type_info

 std::type_info este definit în <typeinfo>


 Pentru fiecare tip se instanțiază un obiect read
only de acest fel
 Membrii:
 name()
 operator ==
Operatorul typeid
 Primește ca argument:
 obiect
 nume de tip
 Returnează const std::type_info&
 Exemplu:
if (typeid(obj) == typeid(A)) { … }
 Tip static (orice tip) – tip dinamic (doar pentru tip
polimorfic)
 Timp constant pentru evaluare
 De testat:
class A {
public:
virtual ~A(){} // cu / fara virtual!
};
class B: public A { };

int main() {
B b;
A& r = b;
A* p = &b;
cout << typeid(r).name() << endl;
cout << typeid(*p).name() << endl;
}
Operatorul dynamic_cast<>
 dynamic_cast<B&> (a) – se încearcă conversia
obiectului la o referință de tip B
 Se folosește de obicei pentru downcasting (tip
părinte  tip fiu)
 Două variante:
 pointer; NULL dacă nu e posibil
 referințe; excepție std::bad_cast dacă nu e
posibil
 Durata depinde de complexitatea lanțului de
moștenire
 Notă: upcasting la moștenire multiplă
Exemplu de folosire dynamic_cast<>:
struct A
{
int i;
virtual ~A () {} //enforce polymorphism; needed for dynamic_cast
};
struct B
{
bool b;
};
struct D: public A, public B
{
int k;
D() { b = true; i = k = 0; }
};
A *pa = new D;
B *pb = dynamic_cast<B*> pa; //cross cast; access the second base
//of a multiple-derived object
static_cast<>
 nu este așa sigur ca dynamic_cast, se bazează
doar pe informațiile de la compilare

 Exemplu:
A* pa = new A();
B* pb;
pb = static_cast<B*> (pa);
class A {
public:
virtual ~A(){}
};
class B: public A { };

int main()
{
A* pa = new B; // new A;
B* pb1 = static_cast<B*>(pa);
B* pb2 = dynamic_cast<B*>(pa);
}
Cast C

class A { };
class B: public A { };
int main()
{
B* pb = new B;
A* pa1 = (A*) pb;
A* pa2 = static_cast<A*>(pb);
return 0;
}
Funcții speciale de conversie de la o clasă la un
tip – suport pentru cast
Exemplu pentru Contor
operator int*() { return &val; }
Contor c;
int *pi = (int*) c;
reinterpret_cast<>

 Convertește un tip pointer la orice alt tip pointer


fără nici o verificare
 Convertește orice tip întreg la pointer și invers!
const_cast<>
 Este folosit pentru a elimina atributele const,
volatile, __unaligned
 Exemplu:
class CCTest {
public:
void setNumber( int );
void printNumber() const;
private:
int number;
};
void CCTest::printNumber() const { // const CCTest* const this
cout << "\nBefore: " << number;
const_cast< CCTest * >( this )->number--;
cout << "\nAfter: " << number;
}
Temă: reimplementați jocul piatră – foarfecă
– hârtie folosind test explicit de tip.
Gestiunea memoriei
 Tipuri de alocare:
 automatic (stivă)
 static (segment date)
 heap (memorie dinamică)
 obiecte excepție
 Funcții de alocare & dealocare
void* operator new(std::size_t) throw(std::bad_alloc); // new
void* operator new[](std::size_t) throw(std::bad_alloc); // new []
void operator delete(void*) throw(); // delete
void operator delete[](void*) throw(); // delete[]
#include <new> - pentru a avea acces la definitia:
std::bad_alloc
std::size_t
 malloc() & free() – doar alocă memorie, nu
inițializează sau distrug obiecte
 Versiuni operator new
 new și excepții – pot genera std:: bad_alloc
 versiune ce returnează NULL
(#include <new>)
void* operator new(std::size_t size, const std::nothrow_t&) throw();
void* operator new[](std::size_t size, const std::nothrow_t&) throw();

char *p = new (nothrow) char [size]; // array nothrow new


if (p != 0) {
//... foloseste p, s-a alocat memoria
delete [] p;
}
plasarea la o adresă (placement new)
int *pi = new int; // new
float *pf = new float[2]; // new []
int *p = new (pi) int (5); // placement new
float *p2 = new (pf) float; // placement new[]
p2[0] = 0.33f;
cout<< *p << p2[0] << endl;
//...
delete pi;
delete [] pf;
 e necesară distrugerea explicită a unui obiect
construit cu un asemenea new:
char * p = new char [sizeof C]; // pre-aloca un buffer
C *pc = new (p) C; // placement new
//... foloseste pc
pc->C::~C(); // se apeleaza explicit destruct
delete [] p;
 Ce se întâmplă dacă apare excepție în constructor:
C* p = new C; -> compilatorul generează următorul
cod:
void __new() throw (bad_alloc)
{
C * p = reinterpret_cast<C*> (new char [sizeof C]); // aloca raw memory
try {
new (p) C; // construieste obiectul in zona alocata
}
catch(...) // prinde toate exceptiile ce pot veni din constructor
{
delete[] p; // elibereaza zona alocata
throw; // re-arunca exceptia din constructor
}
}
Notă: nu se generează memory leak
Alocarea de obiecte mici

 Gestiunea memoriei dinamice în C++ nu este


optimizată (viteză & spațiu) pentru obiecte mici
 New & delete sunt wrappere pentru malloc() si
free() din C
 Discuție: strategii de alocare memorie – blocuri
memorie, header, first/best/random fit
 Andrei Alexandrescu – Modern C++ Design:
Generic Programming and Design Patterns
Applied, Addison Wesley, 2001
Organizare alocator
Chunk
- structură definită în FixedAllocator și folosită doar de ea
- mărimea și numărul blocurilor gestionate este cunoscut
doar de nivelul superior – FixedAllocator
- pData – indică la începutul blocurilor alocate
- firstAvailableBlock_ - ține indexul primului bloc liber
- blocksAvailable_ - ține numărul blocurilor libere
struct Chunk
{
void Init(std::size_t blockSize, unsigned char blocks);
void* Allocate(std::size_t blockSize);
void Deallocate(void* p, std::size_t blockSize);
unsigned char* pData_;
unsigned char firstAvailableBlock_, blocksAvailable_;
};
FixedAllocator

- conține o listă (chunks) de Chunks


- ține informația despre mărimea și numărul blocurilor
gestionate
- dacă nu are spațiu disponibil va aloca un nou Chunk și
va servi clienții de aici
- problemă la dealocare – în ce chunk trebuie înapoiat
blocul? Variante:
- Cache cu blocurile înapoiate
- Folosirea aceleiași strategii ca și chunk – locul ultimei
alocări/dealocări
class FixedAllocator
{
public:
FixedAllocator(std::size_t blockSize, unsigned char
numBlocks);
void* Allocate();
void Deallocate(void* );
private:
std::size_t blockSize_;
unsigned char numBlocks_;
typedef std::vector<Chunk> Chunks;
Chunks chunks_;
Chunk *allocChunk_, *deallocChunk_;
};
SmallObjectAllocator

- SmallObjAllocator oferă metode de alocare și dealocare


a memoriei
- conține câteva obiecte FixedAllocator, câte unul pentru
fiecare mărime “mică”
- Funcție de cerere va alege unul din obiectele
FixedAllocator de la care se va cere spațiu, sau se va
folosi ::new
- constructorul specifică mărimea unui obiect de la care se
consideră mare și pentru care se folosește ::new
class SmallObjAllocator
{
public:
SmallObjAllocator(
std::size_t chunkSize, // nr. blocuri în Chunk
std::size_t maxObjectSize);
void* Allocate(std::size_t numBytes);
void Deallocate(void* p, std::size_t size);
...
private:
std::vector<FixedAllocator> pool_;
FixedAllocator* pLastAlloc_;
FixedAllocator* pLastDealloc_;
};
SmallObject
- Suprascrie operatorii new și delete pentru a redirecționa
lucrul cu memoria dinamică spre SmallObjectAllocator
- Obiectele mici din sistem vor moșteni acest SmallObject
și automat vor folosi alocatorul de obiecte mici;
alocatorul se poate folosi și direct
class SmallObject
{
public:
static void* operator new(std::size_t size);
static void operator delete(void* p, std::size_t size); // !!!
virtual ~SmallObject() {}
};
Templates

 Programare generică
 Specificarea unei familii de clase sau
funcții
 Generarea de clase / cod la compilare
template <class T> class Vector; //declaration

template <class T> class Vector //definition


{
private:
size_t sz;
T * buff;
public:
explicit Vector<T>(size_t s = 100);
Vector<T> (const Vector <T> & v); //copy constructor
Vector<T>& operator= (const Vector<T>& v);//assignment op
~Vector<T>(); //destructor
//other member functions
T& operator [] (unsigned int index);
const T& operator [] (unsigned int index) const;
size_t size() const;
};
template <class T> Vector<T>::Vector(size_t s) : sz(s), buff (new T[s]) { }

template <class T> Vector<T>::Vector (const Vector <T> & v) { ... }

template <class T> Vector<T>& Vector<T>::operator= (const Vector <T> & v) {


if (this == &v) return *this;
this->Vector<T>::~Vector<T>(); //call destructor
buff = new T[v.size()]; //allocate sufficient storage
for (size_t i =0; i < v.size(); i++)
buff[i] = v[i]; //memberwise copy
sz = v.size();
return *this;
}

template <class T> Vector<T>::~Vector () { delete [] buff; }

template <class T> inline T& Vector<T>::operator [] (unsigned int i) { return buff[i]; }

template <class T> inline const T& Vector<T>::operator [] //const version


(unsigned int i) const { return buff[i]; }

template <class T> inline size_t Vector<T>::size () const { return sz; }


template <typename T> class Vector ...

 Intanțierea unui template – se vor instanția doar membrii


funcție folosiți!
Vector<int> vi(5);
 Argumente template
template <class T, int n> class Array
{
private:
T a[n];
int size;
public:
Array() : size (n){}
T& operator [] (int idx) { return a[idx]; }
};

Array<char, 5> ac;

Vector <Vector<char*> > vv;


 Argumente implicite
template <class T, class S = size_t > class Vector {
private:
S sz;
T * buff;
public:
explicit Vector(S s = 100): sz(s), buff(new T[s]) { }
~Vector();
// ...
S size() const;
};
template <class T, class S> Vector<T,S>::~Vector<T,S>() { delete [] buff; }
template <class T, class S> S Vector<T,S>::size() const { return sz; }

int main() {
Vector <int> x;
Vector <int, unsigned char> y(5);
return 0;
}
 Membrii statici
template<class T> class C {
public:
static T stat;
};
template<class T> T C<T>::stat = 5; //definiție

void f() {
int n = C<int>::stat;
}

 Friendship
template <class T> class Vector {
public:
//...
friend void f ();
friend class A;
friend class B<int>;
template <class U> friend class C;
};
 Specializare parțială – oferă o definiție alternativă la
template-ul primar
template <class T> class Vector <T*> //partial specialization of Vector <T>
{
private:
size_t size;
void * p;
public:
Vector();
~Vector();
//...member functions
size_t size() const;
};

template<class T, class U, int i> class A { }; // primary


template<class T, int i> class A<T, T*, i> { }; // partial specialization
template<class T> class A<int, T*, 8> { };
 Specializare explicită – optimizare particulară tipului
parametru
template <> class Vector <bool> //explicit specialization
{
private:
size_t sz;
unsigned char * buff;
public:
explicit Vector(size_t s = 1) : sz(s), buff (new unsigned char [(sz+7U)/8U] ) { }
Vector<bool> (const Vector <bool> & v);
Vector<bool>& operator= (const Vector<bool>& v);
~Vector<bool>();
//other member functions
bool& operator [] (unsigned int index);
const bool& operator [] (unsigned int index) const;
size_t size() const;
};
void bitmanip()
{
Vector< bool> bits(8);
bits[0] = true; //assign
bool seventh = bits[7]; //retrieve
}
 Specializare a metodelor template
În template-ul Vector:
public:
friend bool operator==<T> (const Vector<T>& v1, const Vector<T>& v2); // primary
friend bool operator==<const char*> ( //specialized version
const Vector<const char *>& v1,
const Vector<const char *>& v2);

template <class T> bool operator== ( const Vector<T>& v1, const Vector<T>& v2 ) {
if (v1.size() != v2.size()) return false;
for (size_t i = 0; i<v1.size(); i++) {
if (v1[i] != v2[i]) return false;
}
return true;
}
template <> bool operator== <const char*> (const Vector<const char *>& v1,
const Vector<const char *>& v2) {
if (v1.size() != v2.size()) return false;
for (size_t i = 0; i<v1.size(); i++) {
if (strcmp(v1[i], v2[i]) != 0) return false;
}
return true;
}
 Template-uri funcție
template <class T> T max( T t1, T t2) {
return (t1 > t2) ? t1 : t2;
}
Dacă se oferă variantă pentru int se va folosi prioritar:
int max (int i, int j) {
return (i > j) ? i : j;
}

Exercițiu: policy based programming


Standard Template Library
 Template-uri și supraîncărcarea operatorilor
 Spațiul de nume std
 Conținut:
 Containere
 Iteratori
 Algoritmi
 Bibliotecă numerică <cmath>
 Utilitare – unique_ptr, shared_ptr
Containere
 Este un obiect care poate ține alte obiecte
 Headere
<vector> tablou de T
<list> listă dublu înlănțuită de T
<deque> coadă cu două capete de T
<queue> coadă de T
<stack> stivă de T
<map> tablou asociativ de T
<set> mulțime de T
<bitset> mulțime de valori Boolean
....
 Cerințe pentru elementele din container:
 copy – construibile (copy constructor - copia
și obiectul original sunt identice)
 asignabile - prin asignare se obțin obiecte
identice
 publice - constructor de copiere, constructor
implicit, operator de asignare, destructor
 Realocare spațiu container
 capacity(), size(), resize(), reserve()
 vector<int> vi(1000);
 Acces la elemente [] (rapid) at() (verificare index
 std::out_of_range)
 Operații front și back – push & pop, front() și
back()
 Asignare între containere – copiere valori
 FIFO – queue, deque
Iteratori
 Pointeri generici, dau acces la elementele din
container
 Tipuri: Input, output, forward, bidirecțional, random
 Toate containerele oferă begin() & end()
vector <int> v(10);
int n=0;
for (vector<int>::iterator p = v.begin(); p!=v.end(); p++)
*p = n++;
 const & non-const – begin(), end()
vector<char>::const_iterator cp = v.begin();
 rbegin(), rend() - reverse
 Invalidarea iteratorilor – după modificarea
containerului un iterator poate deveni invalid
Algoritmi
 Se aplică pe containere și secvențe
 Tipuri algoritmi:
 operații fără modificare (read only)
 operații ce modifică secvența (read / write)
 sortare
 find()
list<char> lc;
lc.push_back('A'); lc.push_back('T'); lc.push_back('L');
list<char>::iterator p = find(lc.begin(), lc.end(), 'A'); // find 'A'
if (p != lc.end()) // was 'A' found?
*p = 'S'; // then replace it with 'S'
while (p != lc.end()) //display the modified list
cout<<*p++;
 copy()
list<int> li; vector <int> vi;
li.push_back(1); li.push_back(2);
vi.reserve( li.size() );
copy (li.begin(), li.end(), vi.begin() );
 sort() – folosește operatorii == și < ale elementelor
din container
vector <int> vi;
vi.push_back(7);
vi.push_back(1);
vi.push_back(19);
sort(vi.begin(), vi.end() );
Exemplu:

void draw(Shape* shape)


{
shape ->draw();
}

void refresh(list<Shape*>& shapes)


{
for_each(shapes.begin(), shapes.end(), draw);
}
Obiecte funcție
 Functor – definește operatorul apel de funcție ();
exemplu:
#include <iostream>
#include <vector>
using namespace std;
class negate {
public : //generic negation operator
template < class T > T operator() (T t) const { return -t;}
};
void callback(int n, const negate& neg) // pass a function
// object rather than a function pointer
{
n = neg(n); //invoke the overloaded () oper to negate n
cout << n; }
....
callback(5, negate() ); //output: -5
 priority_queue folosește obiectul funcție less pentru a
sorta elementele intern
#include <functional> // definition of less
#include <queue> // definition of priority_queue
#include <iostream>
using namespace std;
struct Task {
int priority;
friend bool operator < (const Task& t1, const Task& t2);
Task(int p=0) : priority(p) {}
};
bool operator < (const Task& t1, const Task& t2) {
return t1.priority < t2.priority; }
......
priority_queue<Task> scheduler;
scheduler.push(Task(3)); scheduler.push(Task(5));
scheduler.push(Task(1)); scheduler.push(Task(1));
cout<< scheduler.top().priority <<endl; // output 5
Obiecte predicat
 Supraîncarcă operatorul () ce returnează un
bool
#include <functional> //definitions of STL predicates
#include <algorithm> //definition of sort
vector <int> vi;
vi.push_back(9); vi.push_back(5); vi.push_back(10);
sort(vi.begin(), vi.end(), greater<int> () ); // descending order
sort(vi.begin(), vi.end(), less<int> () ); // now in ascending order

// iterator
found = find_if(l.begin(), l.end(), selection);

Int cnt = count_if(l.begin(), l.end(), selection);


Alte containere

 Containere specializate
 vector<bool> - un bool / bit
 Containere asociative (map)
 elemente pair<class Key, class Value>
enum directions {up, down};

pair<string, int> Enumerator(string("down"), down); //create a


// pair
map<string, int> mi; // create a map
mi.insert(Enumerator); // insert the pair
int n = mi["down"]; // n = 1
Altele

 (#include <memory>) auto_ptr<>


auto_ptr<double> dptr(new double(0.0));
C++11:
unique_ptr<>
shared_ptr<>
 string
C11
Uniformizarea inițializărilor
Sintaxa de inițializare cu listă de valori între acolade
s-a extins la toate tipurile (predefinite sau definite de
utilizator)
Se poate folosi sau nu =
int x = {5};
double y {2.75};
short arr[5] {4,5,2,76,1};
int * arr = new int [4] {2,4,6,7};
Carte c1(titlu, autor); // forma clasică (ctor)
Carte c2 {titlu, autor};
Carte c3 = {titlu, autor};
Compilatorul nu face conversie automată la tip mai mic:
int x = 1.2; // warning
int y {1.2}; // error
auto – C11
auto x = 1; // 1. inferare tip variabilă la compilare
auto res = foo();
auto main() -> int // 2. sintaxă alternativă pentru funcții
{
cout << func(3) << endl;
return 0;
}
// 3. inferare valoare de return funcție:
auto divideNumbers(double numerator, double denominator)
{
if (denominator == 0) { /* ... */ }
return numerator / denominator;
}
// 4. pentru expresii lambda generice
// inferare tip variabilă pe baza tipului unei expresii:
int x = 1;
decltype(x) y = 2;

const string message = "Test";


const string& foo() {
return message;
}
auto f = foo(); // auto elimină const și &
decltype(auto) f = foo(); // se păstrează tipul intact
( decltype(foo()) f = foo(); )
nullptr

void f(char* str) {cout << "char* version" << endl;}


void f(int i) {cout << "int version" << endl;}
int main()
{
f(NULL); // care f? –> apel f(nulptr)
return 0;
}
Smart pointers
Header <memory>
unique_ptr
shared_ptr
weak_ptr
Enumerări
enum Culoare {rosu, galben, abastru}; // C style
Culoare c1 = rosu; // rosu este definit în
//domeniul curent de vizibilitate
int c2 = rosu; // tip întreg

enum class Culoare {rosu, galben, abastru};


Culoare c3 = Culoare::rosu;
enum struct Culoare {rosu, galben, abastru};
Clase
explicit – aplicat constructorilor cu un singur
argument dezactivează conversiile explicite
Se aplică acum și operatorilor de conversie:
...
operator int() const;
explicit operator double() const;
Contor a, b;
int n = a; // conversie automată
double x = b; // nu e permis
x = double(b); // conversie explicită este permisă
Template-uri și STL
double vals[5] = {4, 10, 6, 7, 8};
for (int x : vals) // for (auto x: vals)
std::cout << x << std::endl; // for (auto& x: vals)

Containere STL noi:


 forward_list – listă simplu înlănțuită
 unordered_map (multimap, set, multiset) – implementate
prin tabele hash
 array

Metode STL noi: cbegin(), cend(), crbegin(), crend() - const


Referințe rvalue

Referințe lvalue (obișnuite) - doar pentru entități ce au


adresă în memorie (se poate aplica operatorul &).
Referințe rvalue:
int x = 10;
int y = 23;
int && r1 = 13;
int && r2 = x + y;
double && r3 = std::sqrt(2.0);
Semantica move
vector<string> generate() // copy ctor pentru generarea val ret!
{ // transfer info (move)!
vector<string> temp;
// generează vectorul
return temp;
}
Indicăm compilatorului modul de construire obiect:
- Copiere info – copy constructor (primește ca
argument const lvalue reference)
- Transfer info – move constructor (rvalue
reference)
Exemplu:
struct A
{
A() { cout << "default" << endl; }
A(const A&) { cout << "copy" << endl; }
// A(A&&) { cout << "move" << endl; }
};
A f() {
A a;
return a;
}
A x = f();
Asignare:
 Copiere operator=(const A&)
 Transfer (move) operator=(A&&)

Forțare move cu std::move() din <utility>


Funcții (expresii) lambda
Funcții anonime
[] (int x) { return x % 2 == 0; }
Funcții ce se folosesc unde se definesc!
Alternativă la funcții obișnuite și functori
Exemplu:
int cnt = count_if(v.begin(), v.end,
[] (int x) { return x % 2 == 0; }) )
Valoarea de retur se determină pe baza a ce întoarce
return
Capturarea variabilelor automatice exterioare
(closure):
[x] – prin valoare
[&x] – prin referință
[&] – toate prin referință
[=] – toate prin valoare
Combinații: [=, &x] [&, x], …
Variadic templates
Funcții cu număr variabil de parametrii (printf)
Permit pasarea de tipuri (oricâte și orice)
 Template parameter pack – parametru ce
acceptă oricâte (inclusiv zero) argumente
template
 Function parameter pack – parametru funcție ce
acceptă oricâte argumente funcție
#include <iostream>
template<typename T>
void print(T const& t) {
std::cout << t;
}
template<typename First, typename ... Rest>
void print(First const& first, Rest const& ... rest) { // params pack
std::cout << first;
print(rest ...); // params pack expansion
}
int main() {
int i = 10;
char* s = "Hello world";
print("i = ", i, " and s = \"", s, "\"\n");
}
Procese și fire de execuție

 Concurență
 procese
 în cadrul proceselor – fire de execuție
 Motive
 partiționare/separare/specializare funcțională
 eficiență
 Concurența și C++
 acces la resursele SO gazdă; cod specific
platformei (de obicei prin C):
 Unix - IEEE POSIX 1003.1c
 Windows
 biblioteci, framework specializat
(independență de platformă) – BOOST, ACE
 standardul C++11 introduce Standard C++
Thread Library
Fire de execuție – generalități
 Creare fir de execuție – funcție executată
 Terminare – funcția se termină, colaborare
pentru oprirea firelor de execuție
 Prioritate
 Planificarea firelor de execuție (scheduler)
 Stările firelor de execuție
 Acces la resurse comune – acces exclusiv
 Sincronizare fire de execuție
 Comunicare între fire de execuție
Fire de execuție POSIX
 Paralelism în cadrul unui proces
 Thread-urile trăiesc în același spațiu de nume/proces:
 au propria stivă
 au în comun: variabile globale, descriptori fișiere, resurse
proces
 Standard IEEE POSIX 1003.1c (Portable Operating
System Interface)
 există implementări sub orice sistem de operare
 definește un API C
 Programare multithreading:
 Avantaj: specializare/partiționare aplicație
 Dezavantaj: complexitate cod
Configurare în Linux:
 include pthread.h
 #define _REENTRANT - înaintea include-urilor
(compilator –D_REENTRANT)
efect: funcțiile standard devin reentrante,
errno / thread
 linker –lpthread
Configurare în Windows
 http://www.sourceware.org/pthreads-win32/
 adăugare directori include și lib
 adăugare pthreadVC2.lib
 pthreadVC2.dll – lângă executabil
Manipularea firelor de execuție
#include <pthread.h>
int pthread_create(pthread_t *thread,
pthread_attr_t *attr,
void* (*start_routine)(void *),
void *arg);
void pthread_exit(void *retval);
int pthread_join(pthread_t th, void **thread_return);

Vezi exemplul threads: ex 1, 2


Note:
 Funcțiile C de obicei returnează 0 pentru succes
 pthread_t – este o structură opacă ce reprezintă un thread
 Un thread are asociată o funcție pe care trebuie să o
execute; terminarea ei duce la terminarea thread-ului
 Un thread este implicit “joinable”, adică părintele trebuie
să-i citeasca starea finală (prin pthread_join()) pentru a se
elibera resursele asociate lui
 Funcția thread-ului poate primi date de la părinte prin
parametrul void*
 Thread-ul poate trimite date la părinte la terminare
 Valoarea returnată de funcția pe care o execută firul de
execuție se poate specifica și prin pthread_exit()

Alte funcții:
int pthread_cancel(pthread_t th);
int pthread_detach(pthread_t th); // nu este join-able
pthread_t pthread_self();
int pthread_equal(pthread_t t1, pthread_t t2);
Controlul atributelor: mărime stivă, detașat, etc
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

Exemplu de folosire:
pthread_attr_t thread_attr;
pthread_attr_init(&thread_attr);
pthread_attr_setdetachstate(&thread_attr,
PTHREAD_CREATE_DETACHED);
pthread_create(&a_thread, &thread_attr, thread_function,
(void *) message);
pthread_attr_destroy(&thread_attr);
Vezi exemplu: threads 3 (control stack size)
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);


int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);

int pthread_attr_setschedparam(pthread_attr_t *attr, const struct


sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct
sched_param *param);

int pthread_attr_setinheritsched(pthread_attr_t *attr, int inherit);


int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inherit);

int pthread_attr_setscope(pthread_attr_t *attr, int scope);


int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);

int pthread_attr_setstacksize(pthread_attr_t *attr, int scope);


int pthread_attr_getstacksize(const pthread_attr_t *attr, int *scope);
 schedpolicy: SCHED_OTHER (implicit),
SCHED_RR (round robin), SCHED_FIFO.
Ultimele două sunt planificări real time (în cadrul
aceleiași priorități), sunt posibile doar dacă se rulează
sub o identitatea superuser.
 schedparams – parametrii folosiți de o politică de
planificare
 inheritsched - PTHREAD_EXPLICIT_SCHED sau
PTHREAD_INHERIT_SCHED
 scope – controlează cum se calculează
planificarea; în Linux este doar
PTHREAD_SCOPE_SYSTEM
Vezi exemplul threads 6 bis
Terminarea unui thread – cancellation
Un tread poate cere terminarea unui alt thread cu
pthread_calcel()
Un thread poate fi, relativ la cancel, în una din următoarele
stări (int pthread_setcancelstate(int state, int* oldstate)):
 PTHREAD_CANCEL_DISABLE – thread-ul nu se poate
termina prin cancel
 PTHREAD_CANCEL_ENABLE – thread-ul este cancelabil
Tipuri de cancel – modul în care se opresc
( int pthread_setcanceltype(int type, int *oldtype) ):
 THREAD_CANCEL_ASYNCHRONOUS – se pot termina
oricând
 PTHREAD_CANCEL_DEFERRED (implicit) – cererea de
terminare se memorează și se va trata doar în locuri bine
stabilite (cancellation points)
Cancellation points:
 Definite în unele funcții sistem
 Definite de programator, prin apel explicit:
pthread_testcancel() – vezi exemplul thread 4
Cleanup handlers:
 se pot înregistra pentru a se executa automat dacă thread-
ul este cancelled (eliberează mutex-uri, eliberează memorie
alocată dinamic, etc)
 Se pot înregistra mai multe, ele se execută în ordinea
inversă înregistrării lor
void pthread_cleanup_push(void (*routine)(void*), void *arg);
void pthread_cleanup_pop(int execute);
 Dacă execute este nenul se va și executa handlerul
 Dacă thread-ul se termină cu pthread_exit() fără a se mai
deregistra handlerele  ele se vor executa
Vezi exemplul threads 8 după mutex & variabilă condiție
Probleme fundamentale ale programării cu fire de
execuție:
 Accesul la resurse comune – rezolvare prin
asigurarea accesului exclusiv la resurse
 Comunicarea între thread-uri
Semafoare
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t * sem); --
int sem_post(sem_t * sem); ++
int sem_destroy(sem_t * sem);
Vezi exemplul threads 7
Mutex
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

Inițializare statică:
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
Vezi exemplul threads 5
Alte funcții:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *mutex,
const struct timespec *deltatime);
Performanță mutex – cere timp
Deadlock – interblocarea thread-urilor (ordinea de
achiziție!)
Tipuri de mutex:
 PTHREAD_MUTEX_NORMAL - fast mutex
(implicit) – apare deadlock dacă un thread face
lock pe același mutex de două ori!
 PTHREAD_MUTEX_RECURSIVE
 PTHREAD_MUTEX_ERRORCHECK – la al
doilea lock pe același mutex se returnează
EDEADLK în loc de deadlock
Exemplu pentru crearea unui mutex cu verificare de eroare
pthread_mutex_t mtx;
pthread_mutexattr_t mtxAttr;
int s, type;
s = pthread_mutexattr_init(&mtxAttr);
if (s != 0) err();
s = pthread_mutexattr_settype(&mtxAttr,
PTHREAD_MUTEX_ERRORCHECK);
if (s != 0) err();
s = pthread_mutex_init(&mtx, &mtxAttr);
if (s != 0) err();
....
s = pthread_mutexattr_destroy(&mtxAttr);
if (s != 0) err;
Variabile condiție
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex, const struct timespec *abstime);

Vezi exemplul threads 6


Clase C++ wraper peste pthread

Propuneți clase C++ wraper pentru


reprezentarea entităților oferite de pthread:
 Thread
 Mutex
 Variabilă condiție
Pattern-uri legate de concurență

 Execuția unui singur thread – senzori trafic


 Guarded suspension
 Balking

S-ar putea să vă placă și