Se all'inizio dell'opera dici che sulla parete c'è un codice C++, alla fine deve inevitabilmente spararti in un piede.
Bjarne Stroustrup
Dal 31 ottobre al 1 novembre si è svolta a San Pietroburgo la conferenza C++ Russia Piter, uno dei più grandi eventi di programmazione in Russia, organizzata dal JUG Ru Group. Tra i relatori invitati ci sono membri del comitato per la standardizzazione del C++, relatori di CppCon, autori di libri della casa editrice O'Reilly, oltre ai maintainer di progetti come LLVM, libc++ e Boost. La conferenza è rivolta a sviluppatori esperti in C++ che desiderano approfondire la propria expertise e scambiare esperienze in un'interazione diretta. Agli studenti, ai dottorandi e ai docenti universitari vengono offerte piacevoli riduzioni.
L'edizione moscovita della conferenza si potrà visitare già nell'aprile del prossimo anno, nel frattempo i nostri studenti racconteranno cosa di interessante hanno appreso dall'evento che si è appena svolto.

Foto da
Chi siamo
Questo post è stato scritto da due studenti della HSE – San Pietroburgo:
- Lisa Vasilenko – studentessa al quarto anno di laurea triennale, che studia ‘Linguaggi di programmazione’ nell'ambito del programma ‘Matematica applicata e informatica’. Dopo aver conosciuto il linguaggio C++ nel primo anno di università, ha successivamente acquisito esperienza pratica durante tirocini nel settore. La passione per i linguaggi di programmazione in generale e la programmazione funzionale in particolare ha influenzato la scelta delle presentazioni alla conferenza.
- Danila Smirnov – studente al primo anno di laurea magistrale in ‘Programmazione e analisi dei dati’. Già a scuola scriveva problemi olimpici in C++, e poi, in un certo senso, il linguaggio è sempre riemerso nelle sue attività di studio, diventando infine il suo strumento di lavoro principale. Ha deciso di partecipare alla conferenza per migliorare le sue conoscenze e scoprire nuove opportunità.
Nella newsletter, la direzione del dipartimento condivide spesso informazioni su eventi educativi legati alla nostra specializzazione. A settembre abbiamo visto informazioni su C++ Russia e abbiamo deciso di registrarci come ascoltatori. Questa è stata la nostra prima esperienza di partecipazione a conferenze simili.
Struttura della conferenza
Relazioni
Nel corso di due giorni, gli esperti hanno letto 30 relazioni, affrontando molti argomenti caldi: applicazioni ingegnose delle funzionalità del linguaggio per risolvere problemi pratici, futuri aggiornamenti del linguaggio in relazione al nuovo standard, compromessi nel design del C++ e precauzioni da adottare nel lavorare con le loro conseguenze, esempi di architetture interessanti per progetti e alcuni dettagli nascosti sull'infrastruttura del linguaggio. Si sono svolte contemporaneamente 3 presentazioni, di solito due in russo e una in inglese.
Zone di discussione
Dopo le presentazioni, tutte le domande rimaste senza risposta e le discussioni non concluse venivano spostate in zone di comunicazione appositamente designate con i relatori, attrezzate con lavagne per scrivere. Un buon modo per trascorrere la pausa tra le presentazioni con una piacevole conversazione.
Lightning Talks e discussioni informali
Se vuoi fare una breve presentazione, puoi iscriverti sulla lavagna per il Lightning Talk serale e avere cinque minuti per raccontare qualsiasi cosa sull'argomento della conferenza. Ad esempio, una rapida introduzione ai sanitizers per C++ (per alcuni era una novità) o la storia di un bug nella generazione di una sinusoide, che può essere solo ascoltato, ma non visto.
Un altro formato è il dibattito panel "Con il comitato a cuore aperto". Sul palco ci sono alcuni membri del comitato di standardizzazione e sul proiettore un caminetto (ufficialmente per creare un'atmosfera accogliente, ma il motivo "perché TUTTO È IN FIAMME" sembra più divertente), le domande riguardano lo standard e la visione generale del C++, senza accesi dibattiti tecnici e polemiche. È emerso che nel comitato ci sono anche persone reali, che possono non essere completamente sicure di qualcosa o non sapere alcune cose.
Per gli appassionati di polemiche, c'era un terzo evento: la sessione BOF "Go contro C++". Prendiamo un amante di Go e un amante di C++, prima dell'inizio della sessione preparano insieme 100500 diapositive sull'argomento (ad esempio sui problemi con i pacchetti in C++ o sull'assenza di generics in Go), e poi discutono vivacemente tra loro e con il pubblico, mentre il pubblico cerca di comprendere subito due punti di vista. Se inizia una polemica non pertinente, il moderatore interviene e riconcilia le parti. Questo formato si protrae a lungo: alcune ore dopo l'inizio era stata trattata solo la metà delle diapositive. Si è dovuto accelerare molto verso la fine.
Stand dei partner
Negli hall erano presenti i partner della conferenza: agli stand venivano presentati progetti attuali, si offrivano stage e opportunità lavorative, si organizzavano quiz e piccole competizioni, oltre a estrazioni di premi graditi. Alcune aziende hanno addirittura proposto ai partecipanti di affrontare le fasi iniziali dei colloqui, il che può rivelarsi utile per chi è arrivato non solo per ascoltare le relazioni.
Dettagli tecnici delle relazioni
Abbiamo ascoltato le relazioni per entrambi i giorni. A volte era difficile scegliere una relazione tra quelle che si svolgevano in parallelo - ci siamo accordati per dividerci e scambiarci le conoscenze acquisite durante le pause. Anche così, sembra che molto sia rimasto trascurato. Qui volevamo parlare del contenuto di alcune relazioni che ci sono sembrate le più interessanti
Eccezioni in C++ attraverso il prisma delle ottimizzazioni del compilatore, Roman Russev

Diapositiva da
Come è evidente dal titolo, Roman ha esaminato il lavoro con le eccezioni usando l'esempio di LLVM. Anche per chi non utilizza Clang nel proprio lavoro, la relazione può comunque fornire una certa comprensione di come il codice possa essere ottimizzato potenzialmente. Questo perché i programmatori di compilatori e delle relative librerie standard comunicano tra loro e molte soluzioni valide possono coincidere.
Quindi, per gestire un'eccezione è necessario eseguire numerose azioni: chiamare il codice di gestione (se presente) o liberare le risorse a livello attuale e srotolare lo stack verso l'alto. Tutto ciò porta al fatto che per le chiamate che possono generare eccezioni, il compilatore aggiunge istruzioni supplementari. Pertanto, se un'eccezione non viene di fatto sollevata, il programma continuerà a eseguire azioni non necessarie. Per cercare di ridurre i costi operativi, in LLVM ci sono diverse euristiche per identificare situazioni in cui non è necessario aggiungere codice di gestione delle eccezioni o dove si può ridurre il numero di istruzioni “superflue”.
Il relatore analizza circa una decina di esse e mostra le situazioni in cui aiutano ad accelerare l'esecuzione del programma, così come quelle in cui questi metodi non sono applicabili.
Così, Roman Russev guida gli ascoltatori alla conclusione che il codice che contiene operazioni con eccezioni non può sempre essere eseguito con costi operativi nulli, e fornisce i seguenti consigli:
- Nella progettazione delle librerie, è meglio rinunciare alle eccezioni in linea di principio;
- se le eccezioni sono comunque necessarie, è buona norma aggiungere modificatori noexcept (e const) ovunque possibile, in modo che il compilatore possa ottimizzare il più possibile.
In generale, il relatore ha confermato l'opinione che le eccezioni dovrebbero essere utilizzate al minimo o addirittura evitate.
Le diapositive della presentazione sono disponibili al seguente link:
Generators, coroutines e altre dolcezze per il cervello, Adi Shavit

Diapositiva da
Una delle molte presentazioni di questa conferenza, dedicata alle innovazioni di C++20, è stata memorabile non solo per la sua presentazione accattivante, ma anche per la chiara identificazione dei problemi esistenti nella logica di elaborazione delle collezioni (ciclo for, callback).
Adi Shavit evidenzia quelli attuali: i metodi attualmente disponibili attraversano l'intera collezione e non forniscono accesso a uno stato intermedio interno (o lo forniscono in caso di callback, ma con parecchi effetti collaterali sgradevoli, come il famoso Callback Hell). Dovrebbero esserci iteratori, ma anche con questi non è tutto così semplice: non esistono punti di ingresso e uscita comuni (begin → end contro rbegin → rend e così via), ed è poco chiaro quanto tempo itereremo in totale. A partire da C++20, questi problemi vengono risolti!
La prima opzione: ranges. Grazie a un wrapper sopra gli iteratori, otteniamo un'interfaccia comune per l'inizio e la fine dell'iterazione, oltre alla possibilità di composizione. Tutto ciò consente di costruire facilmente pipeline di elaborazione dei dati complete. Ma non tutto è così semplice: parte della logica di calcolo si trova all'interno dell'implementazione di un particolare iteratore, il che può complicare la comprensione e il debug del codice.

Diapositiva da
Bene, per questo C++20 ha introdotto le coroutines (funzioni il cui comportamento è simile ai generators nel linguaggio Python): l'esecuzione può essere rinviata restituendo un certo valore attuale, mantenendo allo stesso tempo uno stato intermedio. In questo modo, raggiungiamo non solo la lavorazione dei dati man mano che emergono, ma anche l'incapsulamento dell'intera logica all'interno di una specifica coroutine.
Ma c'è un rovescio della medaglia: attualmente sono supportati solo parzialmente dai compilatori disponibili e non sono stati implementati con la precisione desiderata. Ad esempio, non è ancora consigliabile utilizzare riferimenti e oggetti temporanei nelle coroutine. Inoltre, ci sono alcune limitazioni su cosa può essere una coroutine, e le funzioni constexpr, i costruttori/distruttori e anche main non rientrano in questa lista.
In questo modo, le coroutine risolvono gran parte dei problemi legati alla semplicità della logica di elaborazione dei dati, ma le loro attuali implementazioni richiedono ulteriori sviluppi.
Materiali:
- Diapositive di C++ Russia —
Trucchi C++ da Yandex.Taxi, Anton Poluchin
Nella mia attività professionale, a volte mi capita di implementare componenti puramente ausiliari: un wrapper tra un'interfaccia interna e l'API di una certa libreria, logging o parsing. Di solito non è necessaria alcuna ottimizzazione aggiuntiva. Ma cosa succede se questi componenti vengono utilizzati in alcuni dei servizi più popolari del Runet? In tal caso, sarà necessario elaborare terabyte di log all'ora! Ogni millisecondo conta, quindi è necessario ricorrere a vari trucchi, dei quali Anton Poluchin ha parlato.
Probabilmente il caso più interessante è stata l'implementazione del pattern pointer-to-implementation (pimpl).
#include <third_party/json.hpp> //PROBLEMS!
struct Value {
Value() = default;
Value(Value&& other) = default;
Value& operator=(Value&& other) = default;
~Value() = default;
std::size_t Size() const { return data_.size(); }
private:
third_party::Json data_;
};In questo esempio, prima di tutto si desidera sbarazzarsi delle intestazioni dei file delle librerie esterne, poiché ciò velocizzerà la compilazione e si potrà proteggere dai possibili conflitti di nomi e altri errori simili.
Bene, abbiamo spostato #include nel file .cpp: è necessaria una forward-declaration dell'API avvolta, così come std::unique_ptr. Ora abbiamo allocazioni dinamiche e altre questioni scomode come dati sparsi nella heap e garanzie ridotte. Tutto ciò può essere affrontato con std::aligned_storage.
struct Value {
// ...
private:
using JsonNative = third_party::Json;
const JsonNative* Ptr() const noexcept;
JsonNative* Ptr() noexcept;
constexpr std::size_t kImplSize = 32;
constexpr std::size_t kImplAlign = 8;
std::aligned_storage_t data_;
};L'unico problema: è necessario specificare la dimensione e l'allineamento per ogni wrapper. Rendi il nostro pimpl template con parametri , usalo con valori arbitrari e aggiungi un controllo nel distruttore per assicurarti di aver indovinato tutto:
~FastPimpl() noexcept {
validate();
Ptr()->~T();
}
template
static void validate() noexcept {
static_assert(
Size == ActualSize,
"Mismatch tra Size e sizeof(T)"
);
static_assert(
Alignment == ActualAlignment,
"Mismatch tra Alignment e alignof(T)"
);
}Poiché il distruttore T è già definito durante l'elaborazione, questo codice verrà analizzato correttamente e durante la fase di compilazione restituirà i valori corretti di dimensione e allineamento che devono essere inseriti. In questo modo, con il costo di un'ulteriore esecuzione della compilazione, ci liberiamo dall'allocazione dinamica delle classi incapsulate, nascondiamo l'API nel file .cpp con l'implementazione e otteniamo inoltre una struttura più adatta alla memorizzazione nella cache del processore.
Il logging e il parsing sono sembrati meno impressionanti, quindi non saranno menzionati in questa recensione.
Le diapositive della presentazione sono disponibili al seguente link:
Tecniche moderne per mantenere il tuo codice DRY, Björn Fahller
In questa presentazione, Björn Fahller mostra diversi modi per affrontare tale difetto stilistico come le verifiche di condizioni ripetute:
assert(a == IDLE || a == CONNECTED || a == DISCONNECTED);Ti sembra familiare? Utilizzando alcune potenti tecniche di C++, emerse negli standard recenti, è possibile implementare elegantemente la stessa funzionalità senza alcuna perdita di prestazioni. Confronta:
assert(a == any_of(IDLE, CONNECTED, DISCONNECTED));Per gestire un numero non fisso di controlli è opportuno utilizzare i template variadic e le fold expressions. Supponiamo di voler controllare se alcune variabili sono uguali all'elemento enumerativo state_type. La prima cosa che ci viene in mente è scrivere una funzione di supporto is_any_of:
enum state_type { IDLE, CONNECTED, DISCONNECTED };
template
bool is_any_of(state_type s, const Ts& ... ts) {
return ((s == ts) || ...);
}
Questo risultato intermedio è deludente. Finora il codice non è diventato più leggibile:
assert(is_any_of(state, IDLE, DISCONNECTING, DISCONNECTED)); Un po' di aiuto per la situazione possono fornire i parametri template non tipo. Con il loro aiuto, trasferiremo gli elementi enumerativi dell'enum in un elenco di parametri template:
template
bool is_any_of(state_type t) {
return ((t == states) | ...);
}
assert(is_any_of(state)); Con l'uso di auto in un parametro template non tipo (C++17), l'approccio si generalizza semplicemente per confronti non solo con gli elementi di state_type, ma anche con tipi primitivi che possono essere utilizzati come parametri template non tipo:
template
bool is_any_of(const T& t) {
return ((t == alternatives) | ...);
}Attraverso tali miglioramenti consecutivi, si raggiunge la sintassi fluida desiderata per le verifiche:
template
struct any_of : private std::tuple {
// ci stanchiamo e ereditamo i costruttori da tuple
using std::tuple::tuple;
template
bool operator ==(const T& t) const {
return std::apply(
[&t](const auto& ... ts) {
return ((ts == t) || ...);
},
static_cast<const std::tuple&>(*this));
}
};
template
any_of(Ts ...) -> any_of;
assert(any_of(IDLE, DISCONNECTING, DISCONNECTED) == state);
In questo esempio, la guida alla deduzione serve a suggerire al compilatore i parametri template desiderati per la struttura, a conoscenza dei tipi degli argomenti del costruttore.
Ora diventa interessante. Bjorn insegna come generalizzare il codice ottenuto per gli operatori di confronto oltre a ==, e poi per operazioni arbitrarie. Contemporaneamente, si spiegano tramite l'uso esempi funzioni come l'attributo no_unique_address (C++20) e i parametri template nelle funzioni lambda (C++20). (Sì, ora la sintassi delle lambda è ancora più facile da ricordare - si tratta di quattro coppie consecutive di parentesi di ogni tipo.) La soluzione finale che utilizza funzioni come pezzi del costruttore è per me estremamente gratificante, per non parlare dell'espressione tuple nelle migliori tradizioni del calcolo lambda.
Alla fine, non dimentichiamo di dare una sistemata:
- Ricordiamo che le lambda sono constexpr gratuitamente;
- Aggiungiamo il perfect forwarding e osserviamo la sua sintassi complessa in relazione al parameter pack in chiusura delle lambda;
- Diamo al compilatore maggiori opportunità per le ottimizzazioni con conditional noexcept;
- Ci preoccuperemo di fornire messaggi di errore più chiari nei template grazie ai valori di ritorno espliciti delle lambda. Questo costringerà il compilatore a eseguire più controlli prima della chiamata effettiva alla funzione template – nella fase di verifica dei tipi.
Per ulteriori dettagli, fare riferimento ai materiali della lezione:
- Diapositive della presentazione:
Le nostre impressioni
La nostra prima partecipazione a C++ Russia è stata memorabile per la sua intensità. Si è avvertito C++ Russia come un evento caloroso, dove il confine tra apprendimento e interazione diretta è quasi impercettibile. Tutto, dall’atteggiamento dei relatori ai concorsi dei partner, incoraggia vivaci discussioni. La parte contenutistica della conferenza, rappresentata dalle relazioni, copre un ampio spettro di argomenti, incluse le novità di C++, esempi pratici di grandi progetti e considerazioni architettoniche ideologiche. Ma sarebbe ingiusto trascurare anche il lato sociale dell'evento, che facilita il superamento delle barriere linguistiche riguardo non solo a C++.
Ringraziamo gli organizzatori della conferenza per l'opportunità di partecipare a un simile evento!
Il post degli organizzatori sul passato, presente e futuro di C++ Russia lo avete potuto vedere .
Grazie per aver letto, e speriamo che il nostro resoconto degli eventi si sia rivelato utile!
Fonte: habr.com
