(nota: ctor = constructor, dtor = destructor)
In caso di errore nel ctor:
new T
lancia un'eccezionenew (nothrow) T
ritorna nullptrOgni tipo è cv-qualified (const-qualified o volatile-qualified) ad eccezione dei function types e dei reference types.
Gli oggetti const non possono essere modificati; le modifiche a gli oggetti volatile sono considerate side effect ai fini dell'ottimizzazione.
(new_type)expr
Non viene fatto nessun controllo logico.
static_cast<new_type>(expr)
Cast tra tipi correlati. new_type
dev'essere cv-qualified almeno quanto old_type
. Utile specie per void*
.
reinterpret_cast<new_type>(expr)
Cast tra tipi non correlati. Reinterpreta i bit in memoria. new_type
dev'essere cv-qualified almeno quanto old_type
.
Man mano che l'eccezione bubbles up the call stack, viene fatto stack unwinding, cioè vengono chiamati i dtor degli oggetti con automatic storage duration.
void f() throw (E1, E2); // May throw any of E1 or E2
void f() throw (); // No exceptions are thrown
void f(); // Any exception may be thrown
Se vengono violate le specifications viene chiamato unexpected()
; posso settare il valore di unexpected con set_unexpected()
.
Garantisco di non modificare l'oggetto e di non chiamare membri non-const.
È l'equivalente di passare per valore, ma senza fare una copia.
Una friend declaration dà accesso a un'altra funzione o classe ai membri private e protected.
class A {
friend class B;
friend void C(A&);
};
class B {};
void C(A&) {
// Can access private members of C
}
Crea un oggetto come copia di un oggetto esistente. Spesso chiamato implicitamente dal compilatore quando un oggetto viene copiato.
Il copy ctor ha accesso a tutti i membri della classe, anche quelli privati.
Class(const Class &to_copy);
"Rubo" le risorse di un oggetto esistente -> diminuisco il consumo di memoria rispetto a una copia. Inoltre serve se ho una risorsa che non voglio condividere con una copia.
Class(const class &&to_move);
Se è necessario esplicitare l'uso del move ctor uso std::move
:
T a(std::move(b));
T a = std::move(b);
f(std::move(a));
return a; // ! Ritornare un valore lo passa per move
Class& operator=(Class&& to_move) noexcept;
Di norma o non implemento nè il move ctor nè il move assignment, o implemento entrambi
Se una classe richiede uno di questi tre, di norma li richiede tutti e tre:
Se una classe implementa i tre metodi sopra, di default le move operations sono definite come deleted. Se voglio le move semantics devo definirle esplicitamente.
Resource Acquisition is Initialization: il lifetime di una risorsa è anche il lifetime della variabile corrispondente. -> Non usare mai new/delete fuori da una classe RAII
Ogni risorsa è incapsulata in una classe che ha la sola responsabilità di gestire la risorsa, acquisirla e rilasciarla. Di norma le copy operations sono deleted e vanno definite delle custom move operations.
RAII+move semantics fanno in modo che ogni risorsa sia owned da uno e un solo oggetto C++ in qualsiasi istante, e per trasferire l'ownership devo fare il move dell'oggetto.
unique_ptr<T>
std::unique_ptr<T>
implementa RAII/ownership semantics per puntatori arbitrari: fa free() quando l'unique_ptr va out of scope.
Ottengo il raw pointer con .get()
, e con .release()
ottengo il puntatore liberando l'ownership.
shared_ptr<T>
Implementa l'ownership condivisa: più oggetti hanno lo stesso puntatore (reference counting, l'ulitmo a perdere l'ownership fa free).
Creati con std::make_shared<T>()
.
std::optional<T>
Value that might or might not exist. Use value()
to access the value, has_value()
to check if it has a value.
std::pair<T, U>
Stores one object of type T and one of type U.
std::tuple<T1, T2, ...>
Stores N objects of types T1, T2, ... Accessed with std::get<i>()
.
Oggetti che immagazzinano una collezione di altri oggetti.
Ce ne sono di diversi tipi; gli iteratori implementano un'interfaccia comune, usata a sua volta dagli algoritmi.
std::vector<T>
Array (-> allocazione contigua) che può crescere dinamicamente.
std::unordered_map<K, V>
Container associativo di coppie key-value. Chiavi uniche.
std::map<K, V>
std::unordered_map<K, V>
ma con chiavi ordinate.
A grandi linee: le letture simultanee su uno stesso container vanno bene, come anche letture e scritture simultanee su container diversi, e modifica di elementi diversi sullo stesso container. Per il resto serve sincronizzazione.
Pointer abstractions: generalizzano i metodi di accesso a container diversi.
Implementano .begin()
, ++
e .end()
.
Esiste una gerarchia di iteratori in base alle funzioni che implementano.
In C++ le funzioni non sono oggetti, per cui di base non posso passarle (solo puntatori a funzione, lambda o classi che implementano stateful function objects).
[captures] (params) -> ret { body }
captures
:
[=]
, [=var]
: by copy[&]
, [&var]
: by referenceCon [=]
catturo tutte le variabili by copy, con [=var]
specifico quali catturare.
std::function<R(T1, T2)>
General-purpose wrapper.
std::sort(iterator first, iterator last)
std::find(iterator first, iterator last, T value)
std::find_if(iterator first, iterator last, (T -> bool) condition)
void foo(int a, int b);
std::thread t1(foo, 123, 456);
std::thread t2([] { foo(123, 456;) }); // Init from lambda
Con .join()
aspetto che un thread finisca.
std::mutex
std::recursive_mutex
std::shared_mutex
: mutex con shared locksstd::unique_lock
: RAII wrapper di mutex
std::shared_lock
: RAII wrapper shared_mutex
Ha lock()
, try_lock()
e unlock()
.
Il recursive mutex permette a un thread di lockare più volte lo stesso thread senza bloccarsi.
std::shared_mutex
ha lock()
, try_lock()
e unlock()
per l'exclusive locking, e lock_shared()
, try_lock_shared()
e unlock_shared()
per lo shared locking.
L'unlock può essere fatto solo dal thread che ha fatto lock.
std::unique_lock
locka il proprio mutex nel ctor (quando acquisisco la risorsa) e lo unlocka nel dtor. Inoltre è movable per trasferire l'ownership del mutex. Ad esempio:
std::mutex m;
int i = 0;
std::thread t([&] {
std::unique_lock l{m}; // Lock
++i;
// Unlock
});
Se ho N lock diversi, std::lock
(e il RAII wrapper std::scoped_lock
) usa un deadlock-avoiding algorithm per acquisirli.
std::mutex m1, m2, m3;
void threadA() {
std::scoped_lock l{m1, m2, m3};
}
Usa un mutex per sincronizzare diversi thread e svegliarli quando una condition variable potrebbe essere modificata. Metodi: wait()
, notify_one()
, notify_all()
.
std::mutex m;
std::condition_variable cv;
std::queue<int> taskQueue;
void pushWork(int task) {
unique_lock l{m};
taskQueue.push(task);
}
cv.notify_one();
}
void workerThread() {
unique_lock l{m};
while(true) {
if (!taskQueue.empty()) {
int task = taskQueue.front();
taskQueue.pop();
l.unlock();
// do something
l.lock();
}
cv.wait(l);
}
}
std::atomic<T>
implementa load()
, store()
, e se wrappa un numero anche fetch_add(T arg)
e fetch_sub(T arg)
(atomic add).
std::future<T>
Con get()
accedo (in modo blocking) a valori che saranno scritti in futuro da un "provider".
std::promise<T>
Una promise immagazzina un valore da recuperare nel future con .get_future()
.
Appena ho il valore chiamo set_value()
per passarlo al future, o set_exception()
(in quel caso .get()
sul future tira l'eccezione).
std::async
Alternativa a std::thread
per eseguire funzioni in parallelo.
std::future<T> async(launch_policy, function, args...);
launch_policy
:
std::launch::async
: il thread viene lanciato immediatamentestd::launch::deferred
: il thread viene lanciato quando si accede al future con .get()
o .wait()
async | deferred
: implementation dependentbool is_prime(int x);
int main () {
std::future<bool> fut = std::async(
std::launch::async, is_prime, 117
);
bool ret = fut.get();
}
std::packaged_task<function_type>
Wrappa un callable element e permette di ottenerne il risultato in modo async.
bool is_prime(int x);
int main() {
packaged_task<bool(int)> tsk(is_prime);
future<int> fut = tsk.get_future();
is_prime(1979);
int r_value = fut.get();
}
std::shared_future<T>
È come un future ma può essere copiato, e diversi future possono avere l'ownership di uno stesso shared state. Inoltre il valore nello shared state può essere ottenuto più volte.
Task-based:
Thread-based: