C++ Rusia: cum a fost

Dacă la începutul piesei spui că pe perete este un cod C++, atunci la final el trebuie neapărat să te împuște în picior.

Bjarne Stroustrup

Între 31 octombrie și 1 noiembrie, la Sankt Petersburg a avut loc conferința C++ Russia Piter – una dintre cele mai mari conferințe de programare din Rusia, organizată de JUG Ru Group. Printre vorbitorii invitați se numără membri ai comitetului de standardizare C++, vorbitori de la CppCon, autori de cărți publicate de O'Reilly, precum și mentori ai unor proiecte precum LLVM, libc++ și Boost. Conferința este destinată dezvoltatorilor experimentați în C++ care doresc să își aprofundeze expertiza și să schimbe idei într-o interacțiune directă. Studenții, absolvenții și profesorii universităților beneficiază de reduceri foarte atractive.

Ediția din Moscova a conferinței va putea fi vizitată în aprilie anul viitor, iar între timp studenții noștri vor povesti ce lucruri interesante au aflat la evenimentul recent. 

C++ Rusia: cum a fost

Fotografie din albumul conferinței

Despre noi

La acest post au lucrat doi studenți ai NIU HSE – Sankt Petersburg:

  • Liza Vasilenko – studentă în anul 4 al programului de licență, studiază specializarea „Limbaje de programare” în cadrul programului „Matematică aplicată și informatică”. A cunoscut limbajul C++ în anul 1 la universitate, iar ulterior a acumulat experiență de lucru cu acesta în cadrul stagiilor din industrie. Pasiunea pentru limbajele de programare în general și pentru programarea funcțională în particular a influențat alegerea discursurilor pentru conferință.
  • Danya Smirnov – student în anul 1 al programului de masterat „Programare și analiză de date”. Încă din școală scria probleme olimpice pe C++, iar apoi, cumva, limbajul a continuat să apară în activitatea sa academică și, în final, a devenit principalul său instrument de lucru. A decis să participe la conferință pentru a-și îmbunătăți cunoștințele, dar și pentru a afla despre noi oportunități.

În newsletter-ul facultății, se împărtășește frecvent informații despre evenimente educaționale legate de specialitatea noastră. În septembrie am văzut informații despre C++ Russia și am decis să ne înregistrăm ca ascultători. Acesta este prima noastră experiență de participare la conferințe de acest tip.

Structura conferinței

  • Prezentări

În decurs de două zile, experții au citit 30 de prezentări, abordând multe subiecte de interes: utilizări ingenioase ale caracteristicilor limbajului pentru rezolvarea problemelor practice, viitoarele actualizări ale limbajului conform noului standard, compromisuri în designul C++ și măsuri de precauție în gestionarea consecințelor acestora, exemple de arhitecturi interesante ale proiectelor, precum și unele detalii tehnice despre infrastructura limbajului. În același timp, au avut loc 3 prezentări simultane, cel mai frecvent două în rusă și una în engleză.

  • Zone de discuție

După prezentări, toate întrebările nelămurite și discuțiile neterminate erau mutate în zone de comunicare special dedicate, echipate cu tablouri cu markere. O modalitate excelentă de a petrece timpul între prezentări într-o discuție plăcută.

  • Lightning Talks și discuții informale

Dacă ai avut dorința de a susține o prezentare scurtă, te puteai înscrie pe tabloul cu markere pentru Lightning Talk-ul de seară și să primești cinci minute pentru a vorbi despre orice subiect legat de conferință. De exemplu, o prezentare rapidă despre sanitizatoare pentru C++ (ceea ce a fost o noutate pentru unii) sau o poveste despre un bug în generarea sinusoidelor, despre care poți auzi, dar nu poți vedea.

Un alt format — o discuție de tip panel "Cu comitetul la suflete". Pe scenă — câțiva membri ai comitetului de standardizare, pe proiector — un șemineu (oficial, pentru a crea o atmosferă plăcută, dar motivul "pentru că TOTUL E ÎN FLĂCĂRI" pare mai amuzant), întrebările — despre standard și viziunea generală asupra C++, fără dezbateri tehnice tumultoase și polemici. S-a dovedit că în comitet sunt și oameni reali care pot fi uneori nesiguri sau să nu știe anumite lucruri.

Pentru iubitorii de polemici, rămânea un al treilea eveniment — sesiunea BOF "Go contra C++". Luăm un fan al limbajului Go, un fan al limbajului C++, înainte de sesiune ei pregătesc împreună 100500 de slide-uri pe tema (cum ar fi problemele cu pachetele în C++ sau absența generics în Go), apoi discută animat între ei și cu publicul, care încearcă să înțeleagă două puncte de vedere. Dacă se începe o polemică inutilă — moderatorul intervine și împacă părțile. Acest format se prelungește: după câteva ore de la început, abia s-a parcurs jumătate din slide-uri. Finalul a trebuit să fie accelerat semnificativ.

  • Standurile partenerilor

În sălile de conferință au fost prezenți partenerii conferinței - la standuri au vorbit despre proiectele curente, au oferit stagii și oportunități de angajare, au organizat quizuri și mici competiții, precum și au oferit premii plăcute. În plus, unele companii au oferit chiar posibilitatea de a trece prin etapele inițiale ale interviurilor, ceea ce poate fi util pentru cei care au venit nu doar pentru a asculta prezentările.

Detalii tehnice ale prezentărilor

Am ascultat prezentările în ambele zile. Uneori, a fost greu să alegem o singură prezentare dintre cele care se desfășurau în paralel - ne-am convenit să ne împărțim și să schimbăm cunoștințele acumulate în pauze. Chiar și așa, pare că multe s-au pierdut. Aici am dori să vorbim despre conținutul unor prezentări care ne-au părut cele mai interesante.

Excepții în C++ prin prisma optimizărilor de compilator, Roman Rusyaev

C++ Rusia: cum a fost
Slide din prezentarea

După cum este evident din titlu, Roman a analizat lucrul cu excepțiile folosind exemplul LLVM. Chiar și pentru cei care nu folosesc Clang în activitatea lor, prezentarea poate oferi o anumită înțelegere despre modul în care codul poate fi potențial optimizat. Aceasta se datorează faptului că dezvoltatorii de compilatoare și biblioteci standard corespunzătoare comunică între ei, iar multe soluții de succes pot corespunde.

Așadar, pentru a gestiona o excepție, este necesar să se efectueze mai multe acțiuni: apelarea codului de tratare (dacă există) sau eliberarea resurselor la nivelul curent și desfășurarea stivei mai sus. Toate acestea duc la faptul că, pentru apelurile care pot genera excepții, compilatorul adaugă instrucțiuni suplimentare. Prin urmare, dacă o excepție nu este de fapt generată, programul va continua să execute totuși acțiuni inutile. Pentru a reduce cheltuielile, LLVM are mai multe heuristicii pentru a defini situațiile în care nu este nevoie de adăugarea codului de tratare a excepțiilor sau unde se poate reduce numărul de instrucțiuni „necesare”.

Prezentatorul examinează aproximativ o duzină dintre acestea și arată atât situațiile în care acestea ajută la accelerarea execuției programului, cât și cele în care aceste metode nu sunt aplicabile.

Astfel, Roman Rusyaev îi conduce pe ascultători spre concluzia că codul care conține lucrul cu excepții nu poate fi întotdeauna executat fără cheltuieli, oferind următoarele sfaturi:

  • Când se dezvoltă biblioteci, ar trebui să se renunțe complet la excepții;
  • dacă totuși excepțiile sunt necesare, ar trebui să se adauge modificatori noexcept (și const), astfel încât compilatorul să poată optimiza cât mai mult posibil.

În general, vorbitorul a confirmat opinia că excepțiile ar trebui utilizate cât mai puțin sau chiar abandonate complet.

Slide-urile prezentării sunt disponibile la linkul: [„Excepții C++ prin prisma optimizărilor compilatorului LLVM”]

Generators, coroutines and other brain-unrolling sweetness, Adi Shavit

C++ Rusia: cum a fost
Slide din prezentarea

Una dintre numeroasele prezentări de la această conferință, dedicată noutăților C++20, s-a remarcat nu doar printr-o prezentare bine realizată, ci și prin claritatea în a evidenția problemele existente în logica procesării colecțiilor (ciclul for, callback-uri).

Adi Shavit identifică următoarele: metodele existente trec prin întreaga colecție și în acelasi timp nu oferă acces la un anumit stadiu intermediar intern (sau oferă în cazul callback-urilor, dar cu multe efecte secundare neplăcute, precum Callback Hell). S-ar putea să existe iteratori, dar nici cu aceștia nu e totul atât de simplu: nu există un punct de intrare și de ieșire comun (begin → end față de rbegin → rend și așa mai departe), nu este clar cât de mult ne vom itera? Începând cu C++20, aceste probleme sunt rezolvate!

Prima opțiune: ranges. Prin intermediul unui wrapper deasupra iteratorilor, obținem o interfață comună pentru începutul și sfârșitul iterației, precum și capacitatea de compunere. Toate acestea permit construirea ușoară a unor procese complete de prelucrare a datelor. Dar nu totul este perfect: o parte din logica calculului se află în interiorul implementării unui anumit iterator, ceea ce poate complica înțelegerea și depanarea codului.

C++ Rusia: cum a fost
Slide din prezentarea

Ei bine, pentru acest caz, C++20 a adăugat corutine (funcții al căror comportament este similar cu generatorii din Python): execuția poate fi amânată, returnând o anumită valoare curentă, păstrând astfel starea intermediară. Astfel, nu doar că lucrăm cu datele pe măsură ce apar, dar encapsulăm întreaga logică în interiorul unei corutine specifice.

Dar există și un aspect negativ: în prezent, acestea sunt doar parțial susținute de compilatoarele existente, iar implementarea lor nu este atât de îngrijită pe cât ne-am dori: de exemplu, momentan nu ar trebui să folosim referințe și obiecte temporare în coroutine. În plus, există anumite restricții cu privire la ceea ce poate fi coroutine, iar funcțiile constexpr, constructorii/destructorii, precum și main nu fac parte din această listă.

Astfel, coroutinele rezolvă o parte semnificativă din problemele legate de ușurința logicii de procesare a datelor, dar implementările lor actuale necesită îmbunătățiri.

Materiale:

Trucuri C++ din Yandex.Taxi, Anton Poluhin

În activitatea mea profesională, uneori este necesar să implementez lucruri pur auxiliare: un wrapper între interfața internă și API-ul unei biblioteci, logare sau parsare. În general, nu este necesară o optimizare suplimentară. Dar ce se întâmplă dacă aceste componente sunt folosite în unele dintre cele mai populare servicii din Runet? În această situație, va trebui să procesăm terabiți pe oră doar din loguri! Atunci fiecare milisecundă contează, așa că trebuie să apelăm la diverse trucuri — despre acestea a povestit Anton Poluhin.

Probabil că cel mai interesant exemplu a fost implementarea modelului 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_; 
};

În acest exemplu, mai întâi ne dorim să scăpăm de fișierele de antet ale bibliotecilor externe — astfel compilarea va fi mai rapidă și ne putem proteja de conflicte de nume și alte erori similare. 

Bine, am mutat #include în fișierul .cpp: avem nevoie de o declarație anticipată a API-ului înfășurat, precum și de std::unique_ptr. Acum avem alocări dinamice și alte probleme neplăcute, cum ar fi datele împrăștiate în heap și garanții reduse. Toate acestea pot fi ajutate de 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_; 
};

Singura problemă: trebuie să specificăm dimensiunea și alinierea pentru fiecare wrapper — să facem pimpl-ul nostru șablon, cu parametrii , să-l folosim cu niște valori arbitrare și să adăugăm în destructor o verificare pentru a ne asigura că am ghicit totul: 

~FastPimpl() noexcept { 
    validate(); 
    Ptr()->~T(); 
}

template 
static void validate() noexcept { 
    static_assert(
        Size == ActualSize, 
        "Size and sizeof(T) mismatch"
    ); 
    static_assert(
        Alignment == ActualAlignment, 
        "Alignment and alignof(T) mismatch"
    ); 
}

Deoarece la procesarea destructorului T este deja definit, acest cod va fi analizat corect în stadiul de compilare, generând erori care vor afișa dimensiunile și alinierea necesare. Astfel, printr-o singură compunere suplimentară, ne eliberăm de alocările dinamice ale claselor îmbrăcate, ascunzând API-ul într-un fișier .cpp cu implementarea, și obținem o structură mai potrivită pentru caching-ul procesorului.

Logarea și parsarea s-au dovedit a fi mai puțin impresionante, de aceea nu vor fi menționate în această recenzia.

Slide-urile prezentării sunt disponibile la linkul: [„Trucurile C++ din Taxi”]

Tehnici moderne pentru a menține codul tău DRY, Björn Fahller

În această prezentare, Björn Fahller arată câteva metode diferite de a combate o problemă stilistică, precum verificările repetitive ale condițiilor:

assert(a == IDLE || a == CONNECTED || a == DISCONNECTED);

Îți sună cunoscut? Folosind câteva tehnici puternice C++, apărute în standardele recente, putem implementa elegant aceeași funcționalitate fără nici o pierdere de performanță. Compară:   

assert(a == any_of(IDLE, CONNECTED, DISCONNECTED));

Pentru a trata un număr nedefinit de verificări, s-ar putea folosi șabloane variadice și expresii fold. Presupunem că dorim să verificăm egalitatea mai multor variabile cu un element din enum-ul state_type. Primul lucru care îți vine în minte este să scrii o funcție auxiliară is_any_of:


enum state_type { IDLE, CONNECTED, DISCONNECTED };

template 
bool is_any_of(state_type s, const Ts& ... ts) { 
    return ((s == ts) || ...); 
}

Acest rezultat intermediar provoacă dezamăgire. Până acum codul nu devine mai citibil:

assert(is_any_of(state, IDLE, DISCONNECTING, DISCONNECTED)); 

Puteți îmbunătăți puțin situația folosind parametrii de șablon non-type. Cu ajutorul acestora, putem muta elementele enumerate din enum într-o listă de parametri de șablon: 

template 
bool is_any_of(state_type t) { 
    return ((t == states) | ...); 
}
	
assert(is_any_of(state)); 

Folosind auto în parametrul de șablon non-type (C++17), abordarea se generalizează și la comparațiile cu nu doar elementele state_type, ci și cu tipuri primitive care pot fi utilizate ca parametri de șablon non-type:


template 
bool is_any_of(const T& t) {
    return ((t == alternatives) | ...);
}

Prin aceste îmbunătățiri succesive se atinge sintaxa dorită pentru verificări:


template 
struct any_of : private std::tuple { 
// ne vom osteni și vom moșteni constructorii de la 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);

În acest exemplu, ghidul de deducție servește pentru a sugera parametrii modelului dorit structurii compilatorului, care cunoaște tipurile argumentelor constructorului. 

Mai departe – devine interesant. Björn învață să generalizeze codul obținut pentru operatorii de comparație în afară de ==, și apoi și pentru operații arbitrare. În același timp, prin exemplificare se explică caracteristici precum atributul no_unique_address (C++20) și parametrii generici în funcțiile lambda (C++20). (Da, acum sintaxa lambda este și mai ușor de reținut – sunt patru perechi consecutive de paranteze de toate tipurile.) Soluția finală folosind funcții ca detalii ale constructorului îmi încălzește foarte mult sufletul, pentru a nu mai vorbi de expresia tuple în cele mai bune tradiții ale calculului lambda.

La final, nu uitați să facem lucrurile să strălucească:

  • Să ne amintim că lambdas – constexpr gratuit; 
  • Să adăugăm perfect forwarding și să ne uităm la sinteza sa neagră aplicată la parameter pack în închiderile lambda;
  • Să oferim compilatorului mai multe posibilități de optimizare cu conditional noexcept; 
  • Să ne asigurăm de o ieșire a erorilor mai clare în șabloane datorită valorilor de returnare explicite ale lambda. Aceasta va face compilatorul să facă mai multe verificări înainte de a apela funcția șablon – în timpul verificării tipurilor. 

Pentru detalii, consultați materialele conferinței: 

Impresiile noastre

Prima noastră participare la C++ Russia a fost memorabilă prin bogăția sa. A existat impresia că C++ Russia este un eveniment plin de suflet, unde granița dintre învățare și interacțiune reală este aproape imperceptibilă. Totul, de la atmosfera vorbitorilor până la concursurile partenerilor evenimentului, încurajează dezbateri fervente. Partea esențială a conferinței, care constă în prezentări, acoperă o gamă destul de largă de subiecte, incluzând noutăți în C++, exemple din practica proiectelor mari și considerații arhitecturale ideologice. Dar ar fi nedrept să nu acordăm atenție și componentei sociale a evenimentului, care ajută la depășirea barierelor lingvistice nu doar în ceea ce privește C++.

Mulțumim organizatorilor conferinței pentru oportunitatea de a participa la un astfel de eveniment!
Postarea organizatorilor despre trecutul, prezentul și viitorul C++ Russia a fost vizibilă în blogul JUG Ru.

Mulțumim pentru lectură și sperăm că relatările noastre despre eveniment au fost utile!

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster