C++ Russland: hvordan det skjedde

Hvis du i begynnelsen av stykket sier at det er C++-kode som henger på veggen, så vil den mot slutten skyte deg i foten.

Bjarne Stroustrup

Fra 31. oktober til 1. november ble C++ Russia Piter-konferansen holdt i St. Petersburg – en av de storstilte programmeringskonferansene i Russland, arrangert av JUG Ru Group. Gjesteforelesere inkluderer medlemmer av C++ Standards Committee, CppCon-høyttalere, O'Reilly-bokforfattere og vedlikeholdere av prosjekter som LLVM, libc++ og Boost. Konferansen er rettet mot erfarne C++-utviklere som ønsker å utdype sin ekspertise og utveksle erfaringer innen live kommunikasjon. Studenter, hovedfagsstudenter og universitetslærere får svært gode rabatter.

Moskva-utgaven av konferansen vil være tilgjengelig for besøk allerede i april neste år, men i mellomtiden vil studentene våre fortelle deg hvilke interessante ting de lærte ved det siste arrangementet. 

C++ Russland: hvordan det skjedde

Foto fra konferansealbum

om oss

To studenter fra National Research University Higher School of Economics - St. Petersburg jobbet med dette innlegget:

  • Liza Vasilenko er en 4. års bachelorstudent som studerer programmeringsspråk som en del av Applied Mathematics and Computer Science-programmet. Etter å ha blitt kjent med C++-språket i mitt første år på universitetet, fikk jeg senere erfaring med å jobbe med det gjennom praksisplasser i bransjen. Lidenskapen for programmeringsspråk generelt og funksjonell programmering spesielt satte sitt preg på valg av rapporter på konferansen.
  • Danya Smirnov er 1. års student på masterprogrammet "Programmering og dataanalyse". Mens jeg fortsatt var på skolen skrev jeg olympiadeoppgaver i C++, og så skjedde det på en eller annen måte at språket stadig kom opp i pedagogiske aktiviteter og til slutt ble hovedarbeidsspråket. Jeg bestemte meg for å delta på konferansen for å forbedre min kunnskap og også lære om nye muligheter.

I nyhetsbrevet deler fakultetsledelsen ofte informasjon om utdanningsarrangement knyttet til vår spesialitet. I september så vi informasjon om C++ Russland og bestemte oss for å registrere oss som lyttere. Dette er vår første erfaring med å delta på slike konferanser.

Konferansestruktur

  • Rapporter

I løpet av to dager leste eksperter 30 rapporter som dekker mange hete emner: genial bruk av språkfunksjoner for å løse anvendte problemer, kommende språkoppdateringer i forbindelse med den nye standarden, kompromisser i C++-design og forholdsregler når man arbeider med konsekvensene, eksempler interessant prosjektarkitektur, i tillegg til noen detaljer om språkinfrastrukturen under panseret. Tre forestillinger fant sted samtidig, oftest to på russisk og en på engelsk.

  • Diskusjonssoner

Etter talen ble alle ustilte spørsmål og uferdige diskusjoner overført til spesielt utpekte områder for kommunikasjon med talerne, utstyrt med merketavler. En god måte å fjerne pausen mellom talene med en hyggelig samtale.

  • Lynsamtaler og uformelle diskusjoner

Hvis du vil gi en kort rapport, kan du melde deg på tavlen for kveldens Lynprat og få fem minutter tid til å snakke om hva som helst om konferansens tema. For eksempel en rask introduksjon til rensemidler for C++ (for noen var det nytt) eller en historie om en feil i sinusbølgegenerering som bare kan høres, men ikke sees.

Et annet format er paneldiskusjonen "Med hjerte til hjerte-komité." På scenen er noen medlemmer av standardiseringskomiteen, på projektoren er en peis (offisielt - for å skape en oppriktig atmosfære, men grunnen "fordi ALT BRENNER" virker morsommere), spørsmål om standarden og den generelle visjonen til C++ , uten heftige tekniske diskusjoner og holiwars. Det viste seg at utvalget også inneholder levende mennesker som kanskje ikke er helt sikre på noe eller kanskje ikke vet noe.

For fans av holivar forble den tredje begivenheten på saken - BOF-økten "Go vs. C++". Vi tar en Go-elsker, en C++-elsker, før øktens start forbereder de sammen 100500 XNUMX lysbilder om et emne (som problemer med pakker i C++ eller mangelen på generikk i Go), og så har de en livlig diskusjon seg imellom og med publikum, og publikum prøver å forstå to synspunkter samtidig. Hvis en holivar starter utenfor kontekst, griper moderatoren inn og forsoner partene. Dette formatet er vanedannende: flere timer etter starten var bare halvparten av lysbildene fullført. Slutten måtte fremskyndes kraftig.

  • Partner står

Konferansens partnere var representert i salene - på standene snakket de om aktuelle prosjekter, tilbød praksisplasser og ansettelse, holdt quiz og småkonkurranser, og lot også lodde ut fine premier. Samtidig tilbød noen selskaper til og med å gå gjennom de innledende stadiene av intervjuer, noe som kunne være nyttig for de som kom ikke bare for å lytte til rapporter.

Tekniske detaljer i rapportene

Vi hørte på rapporter begge dagene. Til tider var det vanskelig å velge én rapport fra de parallelle – vi ble enige om å dele opp og utveksle kunnskapen vi fikk i pausene. Og likevel ser det ut til at mye er utelatt. Her vil vi gjerne snakke om innholdet i noen av rapportene som vi fant mest interessante

Unntak i C++ gjennom prismet til kompilatoroptimaliseringer, Roman Rusyaev

C++ Russland: hvordan det skjedde
Skyv fra presentasjoner

Som tittelen antyder, så Roman på å arbeide med unntak ved å bruke LLVM som eksempel. Samtidig, for de som ikke bruker Clang i sitt arbeid, kan rapporten fortsatt gi en ide om hvordan koden potensielt kan optimaliseres. Dette er fordi utviklerne av kompilatorer og tilsvarende standardbiblioteker kommuniserer med hverandre og mange vellykkede løsninger kan falle sammen.

Så, for å håndtere et unntak, må du gjøre mange ting: ringe håndteringskoden (hvis noen) eller gratis ressurser på gjeldende nivå og spinne opp stabelen høyere. Alt dette fører til det faktum at kompilatoren legger til ytterligere instruksjoner for samtaler som potensielt gir unntak. Derfor, hvis unntaket faktisk ikke oppheves, vil programmet fortsatt utføre unødvendige handlinger. For på en eller annen måte å redusere overhead, har LLVM flere heuristikker for å bestemme situasjoner der unntakshåndteringskode ikke trenger å legges til eller antallet "ekstra" instruksjoner kan reduseres.

Foredragsholderen undersøker omtrent et dusin av dem og viser både situasjoner der de bidrar til å fremskynde programkjøringen, og de der disse metodene ikke er anvendelige.

Dermed leder Roman Rusyaev studentene til den konklusjon at kode som inneholder unntakshåndtering ikke alltid kan utføres med null overhead, og gir følgende råd:

  • når du utvikler biblioteker, er det verdt å forlate unntak i prinsippet;
  • hvis det fortsatt er behov for unntak, så når det er mulig er det verdt å legge til noexcept (og const) modifikatorer overalt slik at kompilatoren kan optimalisere så mye som mulig.

Generelt bekreftet foredragsholderen synet om at unntak best brukes til et minimum eller forkastes helt.

Rapportslidene er tilgjengelige på følgende lenke: ["C++-unntak gjennom linsen til LLVM-kompilatoroptimaliseringer"]

Generatorer, koroutiner og annen hjerneutrullende sødme, Adi Shavit

C++ Russland: hvordan det skjedde
Skyv fra presentasjoner

En av de mange rapportene på denne konferansen dedikert til innovasjoner i C++20 ble minneverdig ikke bare for sin fargerike presentasjon, men også for sin klare identifikasjon av eksisterende problemer med innsamlingsbehandlingslogikken (for loop, tilbakeringing).

Adi Shavit fremhever følgende: de for øyeblikket tilgjengelige metodene går gjennom hele samlingen og gir ikke tilgang til en intern mellomtilstand (eller de gjør det i tilfelle tilbakeringinger, men med et stort antall ubehagelige bivirkninger, som for eksempel Callback Hell) . Det ser ut til at det er iteratorer, men selv med dem er ikke alt så jevnt: det er ingen vanlige inngangs- og utgangspunkter (begynn → slutt versus rbegin → rend og så videre), det er ikke klart hvor lenge vi vil iterere? Fra og med C++20 er disse problemene løst!

Første alternativ: områder. Ved å pakke iteratorer får vi et felles grensesnitt for begynnelsen og slutten av en iterasjon, og vi får også muligheten til å komponere. Alt dette gjør det enkelt å bygge fullverdige databehandlingsrørledninger. Men ikke alt er så glatt: en del av beregningslogikken er plassert inne i implementeringen av en spesifikk iterator, noe som kan komplisere koden å forstå og feilsøke.

C++ Russland: hvordan det skjedde
Skyv fra presentasjoner

Vel, for dette tilfellet har C++20 lagt til koroutiner (funksjoner hvis oppførsel ligner på generatorer i Python): utførelse kan utsettes ved å returnere en gjeldende verdi samtidig som en mellomtilstand bevares. Dermed oppnår vi ikke bare å jobbe med data slik de fremstår, men også å kapsle inn all logikken inne i en spesifikk korutin.

Men det er en flue i salven: for øyeblikket støttes de bare delvis av eksisterende kompilatorer, og er heller ikke implementert så pent som vi ønsker: for eksempel er det ennå ikke verdt å bruke referanser og midlertidige objekter i koroutiner. I tillegg er det noen begrensninger på hva som kan være korutiner, og constexpr-funksjoner, konstruktører/destruktorer og hoved er ikke inkludert i denne listen.

Dermed løser korutiner en betydelig del av problemene med enkelheten til databehandlingslogikk, men deres nåværende implementeringer krever forbedring.

materialer:

C++-triks fra Yandex.Taxi, Anton Polukhin

I mine profesjonelle aktiviteter må jeg noen ganger implementere rene hjelpeting: en innpakning mellom det interne grensesnittet og API-en til et eller annet bibliotek, logging eller parsing. I dette tilfellet er det vanligvis ikke behov for ytterligere optimalisering. Men hva om disse komponentene brukes i noen av de mest populære tjenestene på RuNet? I en slik situasjon må du behandle terabyte per time med logger alene! Da teller hvert millisekund og derfor må du ty til ulike triks – Anton Polukhin snakket om dem.

Det kanskje mest interessante eksemplet var implementeringen av peker-til-implementeringsmønsteret (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_; 
};

I dette eksemplet vil jeg først bli kvitt headerfilene til eksterne biblioteker - dette vil kompilere raskere, og du kan beskytte deg mot mulige navnekonflikter og andre lignende feil. 

Okei, vi flyttet #include til .cpp-filen: vi trenger en videresendingserklæring for den innpakket API, samt std::unique_ptr. Nå har vi dynamiske allokeringer og andre ubehagelige ting som data spredt over en haug med data og reduserte garantier. std::aligned_storage kan hjelpe med alt dette. 

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<kImplSize, kImplAlign> data_; 
};

Det eneste problemet: vi må spesifisere størrelsen og justeringen for hver innpakning - la oss lage vår kvisemal med parametere , bruke noen vilkårlige verdier og legge til en sjekk til destruktoren at vi har gjettet alt riktig : 

~FastPimpl() noexcept { 
    validate<sizeof(T), alignof(T)>(); 
    Ptr()->~T(); 
}

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

Siden T allerede er definert når destruktoren behandles, vil denne koden bli analysert riktig og på kompileringsstadiet vil den gi ut de nødvendige størrelsene og justeringsverdiene som må angis som feil. På bekostning av en ekstra kompileringskjøring blir vi dermed kvitt den dynamiske allokeringen av innpakket klasser, skjuler API i en .cpp-fil med implementeringen, og får også et design som er mer egnet for caching av prosessoren.

Logging og parsing virket mindre imponerende og vil derfor ikke bli nevnt i denne anmeldelsen.

Rapportslidene er tilgjengelige på følgende lenke: ["C++-triks fra Taxi"]

Moderne teknikker for å holde koden din TØRR, Björn Fahller

I denne foredraget viser Björn Fahller flere forskjellige måter å bekjempe den stilistiske feilen ved gjentatte tilstandskontroller:

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

Høres kjent ut? Ved å bruke flere kraftige C++-teknikker introdusert i nyere standarder, kan du elegant implementere den samme funksjonaliteten uten ytelsesstraff. Sammenligne:   

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

For å håndtere et ufast antall sjekker, må du umiddelbart bruke variadiske maler og fold-uttrykk. La oss anta at vi ønsker å sjekke likheten mellom flere variabler til enumens state_type-element. Det første du tenker på er å skrive en hjelpefunksjon er_noen_av:


enum state_type { IDLE, CONNECTED, DISCONNECTED };

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

Dette mellomresultatet er skuffende. Så langt blir ikke koden mer lesbar:

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

Ikke-type malparametere vil bidra til å forbedre situasjonen litt. Med deres hjelp vil vi overføre de tallrike elementene i enumet til listen over malparametere: 

template <state_type ... states>
bool is_any_of(state_type t) { 
    return ((t == states) | ...); 
}
	
assert(is_any_of<IDLE, DISCONNECTING, DISCONNECTED>(state)); 

Ved å bruke auto i en ikke-type malparameter (C++17), generaliserer tilnærmingen ganske enkelt til sammenligninger ikke bare med state_type-elementer, men også med primitive typer som kan brukes som ikke-type-malparametere:


template <auto ... alternatives, typename T>
bool is_any_of(const T& t) {
    return ((t == alternatives) | ...);
}

Gjennom disse suksessive forbedringene oppnås den ønskede flytende syntaksen for sjekker:


template <class ... Ts>
struct any_of : private std::tuple<Ts ...> { 
// поленимся и унаследуем конструкторы от tuple 
        using std::tuple<Ts ...>::tuple;
        template <typename T>
        bool operator ==(const T& t) const {
                return std::apply(
                        [&t](const auto& ... ts) {
                                return ((ts == t) || ...);
                        },
                        static_cast<const std::tuple<Ts ...>&>(*this));
        }
};

template <class ... Ts>
any_of(Ts ...) -> any_of<Ts ... >;
 
assert(any_of(IDLE, DISCONNECTING, DISCONNECTED) == state);

I dette eksemplet tjener deduksjonsguiden til å foreslå de ønskede strukturmalparametrene til kompilatoren, som kjenner typene av konstruktørargumentene. 

Videre - mer interessant. Bjørn lærer hvordan man generaliserer den resulterende koden for sammenligningsoperatorer utover ==, og deretter for vilkårlige operasjoner. Underveis blir slike funksjoner som no_unique_address-attributt (C++20) og malparametere i lambda-funksjoner (C++20) forklart ved hjelp av eksempler på bruk. (Ja, nå er lambda-syntaksen enda lettere å huske - dette er fire påfølgende par med parenteser av alle slag.) Den endelige løsningen med funksjoner som konstruktørdetaljer varmer virkelig sjelen min, for ikke å snakke om uttrykket tuppel i de beste lambda-tradisjonene kalkulus.

På slutten, ikke glem å polere den opp:

  • Husk at lambdas er constexpr gratis; 
  • La oss legge til perfekt videresending og se på dens stygge syntaks i forhold til parameterpakken i lambda-lukkingen;
  • La oss gi kompilatoren flere muligheter for optimaliseringer med betinget noexcept; 
  • La oss ta vare på mer forståelig feilutgang i maler takket være eksplisitte returverdier for lambdaer. Dette vil tvinge kompilatoren til å gjøre flere kontroller før malfunksjonen faktisk kalles - på typekontrollstadiet. 

For detaljer, se forelesningsmateriellet: 

Våre inntrykk

Vår første deltakelse i C++ Russland ble minneverdig for sin intensitet. Jeg fikk inntrykk av C++ Russland som en oppriktig begivenhet, hvor grensen mellom trening og direkte kommunikasjon er nesten umerkelig. Alt, fra stemningen til foredragsholderne til konkurransene fra arrangementspartnerne, bidrar til heftige diskusjoner. Innholdet i konferansen, som består av rapporter, dekker et ganske bredt spekter av temaer, inkludert C++-innovasjoner, casestudier av store prosjekter og ideologiske arkitekturbetraktninger. Men det ville være urettferdig å ignorere den sosiale komponenten i arrangementet, som hjelper til med å overvinne språkbarrierer i forhold til ikke bare C++.

Vi takker konferansearrangørene for muligheten til å delta på et slikt arrangement!
Du har kanskje sett arrangørens innlegg om fortiden, nåtiden og fremtiden til C++ Russland på JUG Ru-bloggen.

Takk for at du leste, og vi håper vår gjenfortelling av hendelser var nyttig!

Kilde: www.habr.com

Legg til en kommentar