Esistono numerosi esempi che evidenziano la forte vicinanza tra i linguaggi C e C++, e come essa si estrinsechi più a livello sintattico che semantico. Anche alla luce delle ultime innovazioni dello standard C++, dovrebbe risultare più chiaro il fatto che C++ non può più essere considerato un'estenzione del linguaggio C, ma un linguaggio compatibile.
La gestione dei flussi standard in C++ è un esempio di come questa compatibilità si applichi non solo alla definizione dei costrutti, ma anche all'implementazione delle librerie dello standard, di modo da preservare l'interoperabilità con le API C.
Sebbene gli stream standard di C++ (std::cout, std::cin, etc.) siano entità distinte e separate dai tradizionali canali standard di C (stdout, stdin e stderr), lo stato dei due sistemi di I/O è, per default, sincronizzato.
Ciò rende possibile alternare liberamente funzioni di I/O proprie di C e di C++, senza che ciò si traduca in incongruenze tra l'ordine di invocazione e quello di esecuzione delle singole direttive di I/O.
Nel listato seguente, ad esempio, è garantito che l'output del programma sia sempre "1 2 3", anche se esso è smistato sui rispettivi canali standard di output di C e C++.
#include <iostream>
#include <cstdio>
int main()
{
std::cout << "1\n";
std::printf("2\n");
std::cout << "3\n";
}
Questo comportamento può essere modificato mediante l'invocazione di una delle API della classe std::ios_base, come mostrato nel listato seguente dove, l'output prodotto potrebbe essere indifferentemente "1 2 3" e "2 1 3", poichè la riproduzione dell'output dei due sistemi di I/O è totalmente indipendente:
#include <iostream>
#include <cstdio>
int main()
{
std::ios::sync_with_stdio(false); // disabilita la sincronizzazione I/O tra C e C++
std::cout << "1\n";
std::printf("2\n");
std::cout << "3\n";
}
L'unico motivo per cui all'utente è data la possibilità di disabilitare la sincronizzazione, consiste nel fatto che essa ha un impatto sulle prestazioni generali del sistema di I/O di C++, soprattutto quando una mole ingente di dati deve essere riprodotta sulla riga di comando. Tuttavia questa soluzione non è tecnicamente praticabile per tutte le applicazioni che richiamano le API del linguaggio C, anche mediante l'inclusione di librerie esterne, in quanto rischia di alterare severamente il comportamento atteso.
Inoltre, se la desincronizzazione avviene dopo aver già effettuato delle chiamate alle funzioni di I/O degli stream standard, non vi è alcuna garanzia che i dati eventualmente presenti nei buffer siano preservati. Questo aspetto non è infatti regolamentato dallo standard, ed è di fatto demandato alle singole implementazioni.
Si osservi, infine, che questo tipo di sincronizzazione ha una valenza diversa rispetto a quella che abbiamo visto tra canali di input e output di C++ mediante l'uso del metodo sync()
della classe std::istream, che serve a preservare la sequenzialità tra input e output relativi ad entità interne alla Standard Stream Library di C++.
Thread-safety degli stream standard di C++
Un altro contesto, differente ed enucleato dai casi che abbiamo finora analizzato, in cui il termine sincronizzazione ha una valenza rilevante è il caso di applicazioni multi-programmate.
Per il linguaggio C++, il modo in cui vengono gestite chiamate alle funzioni di I/O fatte da thread concorrenti varia in base allo standard di riferimento.
Per gli standard ante C++11, invocazioni concorrenti dei metodi degli stream standard possono degenerare nella corruzione dello stato degli stream stessi, sia per il contenuto del buffer, sia per le opzioni di formattazione.
A partire dallo standard C++11, sono state introdotte delle politiche di gestione delle race condition che consentono di regolare l'accesso da parte di thread concorrenti agli stream, in modo tale da non corromperne lo stato. Tuttavia, ciò non garantisce affatto che l'output o l'input siano congruenti senza l'uso di opportune politiche di sincronizzazione che possano evitare l'interlacciamento di dati immessi/estratti da thread differenti.
Ad esempio, per eseguibili compilati con il supporto allo standard C++11 o superiore, le istruzioni std::cout << "12"
e std::cout << "34"
eseguite in thread distinti, potrebbero generare indifferentemente l'output "1324" o "3412" o ancora "1342". Tuttavia, a prescindere dall'ordine e a differenza da quando prescritto dagli standard precedenti, non vi sarebbe alcuna perdita di dati e tutti i caratteri sarebbero riprodotti a schermo.