C++ Ryssland: hur det hände

Om du i början av pjäsen säger att det hänger C++-kod på väggen, då är det skyldigt att skjuta dig i foten i slutet.

Bjarne Stroustrup

Från 31 oktober till 1 november hölls C++ Russia Piter-konferensen i St. Petersburg – en av de storskaliga programmeringskonferenserna i Ryssland, anordnad av JUG Ru Group. Inbjudna talare inkluderar medlemmar av C++ Standards Committee, CppCon-talare, O'Reilly-bokförfattare och underhållare av projekt som LLVM, libc++ och Boost. Konferensen vänder sig till erfarna C++-utvecklare som vill fördjupa sin expertis och utbyta erfarenheter inom livekommunikation. Studenter, doktorander och universitetslärare får mycket fina rabatter.

Moskva-upplagan av konferensen kommer att finnas tillgänglig för besök redan i april nästa år, men under tiden kommer våra elever att berätta för dig vilka intressanta saker de lärde sig vid det senaste evenemanget. 

C++ Ryssland: hur det hände

Foto från konferensalbum

om oss

Två studenter från National Research University Higher School of Economics - St. Petersburg arbetade med detta inlägg:

  • Liza Vasilenko är en 4:e års student som studerar programmeringsspråk som en del av programmet tillämpad matematik och datavetenskap. Efter att ha bekantat mig med språket C++ under mitt första år på universitetet, fick jag sedan erfarenhet av att arbeta med det genom praktik i branschen. Min passion för programmeringsspråk i allmänhet och funktionell programmering i synnerhet satte sin prägel på urvalet av rapporter på konferensen.
  • Danya Smirnov är 1:a årsstudent på masterprogrammet "Programmering och dataanalys". Medan jag fortfarande gick i skolan skrev jag olympiadproblem i C++, och sedan hände det på något sätt att språket ständigt kom upp i pedagogisk verksamhet och så småningom blev det huvudsakliga arbetsspråket. Jag bestämde mig för att delta i konferensen för att förbättra mina kunskaper och även lära mig om nya möjligheter.

I nyhetsbrevet delar fakultetsledningen ofta information om utbildningsevenemang relaterade till vår specialitet. I september såg vi information om C++ Ryssland och bestämde oss för att registrera oss som lyssnare. Detta är vår första erfarenhet av att delta i sådana konferenser.

Konferensstruktur

  • Rapporter

Under två dagar läste experter 30 rapporter som täcker många heta ämnen: genialisk användning av språkfunktioner för att lösa tillämpade problem, kommande språkuppdateringar i samband med den nya standarden, kompromisser i C++-design och försiktighetsåtgärder när man arbetar med deras konsekvenser, exempel intressant projektarkitektur, samt några underhuvens detaljer om språkinfrastrukturen. Tre föreställningar ägde rum samtidigt, oftast två på ryska och en på engelska.

  • Diskussionszoner

Efter talet överfördes alla oställda frågor och oavslutade diskussioner till särskilt utsedda områden för kommunikation med talarna, utrustade med markeringstavlor. Ett bra sätt att fördriva pausen mellan talen med ett trevligt samtal.

  • Blixtsamtal och informella diskussioner

Om du vill ge en kort rapport kan du anmäla dig på whiteboarden till kvällens Lightning Talk och få fem minuters tid att prata om vad som helst om konferensens ämne. Till exempel en snabb introduktion till desinfektionsmedel för C++ (för vissa var det nytt) eller en berättelse om en bugg i sinusvågsgenerering som bara kan höras men inte ses.

Ett annat format är paneldiskussionen "Med hjärta till hjärta-kommittén." På scenen finns några medlemmar av standardiseringskommittén, på projektorn finns en öppen spis (officiellt - för att skapa en uppriktig atmosfär, men anledningen "för att ALLT BRÄNDER" verkar roligare), frågor om standarden och den allmänna visionen av C++ , utan hetsiga tekniska diskussioner och holiwars. Det visade sig att nämnden även innehåller levande människor som kanske inte är helt säkra på något eller kanske inte vet något.

För fans av holivar återstod den tredje händelsen på fallet - BOF-sessionen "Go vs. C++". Vi tar en Go-älskare, en C++-älskare, innan sessionens början förbereder de tillsammans 100500 XNUMX bilder om ett ämne (som problem med paket i C++ eller bristen på generika i Go), och sedan har de en livlig diskussion sinsemellan och med publiken, och publiken försöker förstå två synpunkter samtidigt. Om en holivar börjar ur sitt sammanhang, ingriper moderatorn och försonar parterna. Det här formatet är beroendeframkallande: flera timmar efter starten var bara hälften av bilderna klara. Slutet måste påskyndas rejält.

  • Partner står

Konferensens samarbetspartners var representerade i salarna – på montrarna pratade de om aktuella projekt, erbjöd praktikplatser och anställning, höll frågesporter och småtävlingar och lottade även ut fina priser. Samtidigt erbjöd sig vissa företag till och med att gå igenom de inledande stadierna av intervjuer, vilket kan vara användbart för dem som inte bara kom för att lyssna på rapporter.

Tekniska detaljer i rapporterna

Vi lyssnade på rapporter båda dagarna. Ibland var det svårt att välja en rapport från de parallella – vi kom överens om att dela upp oss och utbyta kunskaper som vi fick under rasterna. Och trots det verkar det som att mycket är utelämnat. Här skulle vi vilja prata om innehållet i några av de rapporter som vi tyckte var mest intressanta

Undantag i C++ genom prismat av kompilatoroptimeringar, Roman Rusyaev

C++ Ryssland: hur det hände
Skjut från presentationer

Som titeln antyder tittade Roman på att arbeta med undantag med hjälp av LLVM som exempel. Samtidigt, för de som inte använder Clang i sitt arbete, kan rapporten ändå ge en uppfattning om hur koden potentiellt skulle kunna optimeras. Detta beror på att utvecklarna av kompilatorer och motsvarande standardbibliotek kommunicerar med varandra och många framgångsrika lösningar kan sammanfalla.

Så för att hantera ett undantag måste du göra många saker: anropa hanteringskoden (om någon) eller gratisresurser på den aktuella nivån och snurra upp stacken högre. Allt detta leder till att kompilatorn lägger till ytterligare instruktioner för anrop som potentiellt skapar undantag. Därför, om undantaget inte faktiskt tas upp, kommer programmet fortfarande att utföra onödiga åtgärder. För att på något sätt minska overhead har LLVM flera heuristiker för att bestämma situationer där undantagshanteringskod inte behöver läggas till eller antalet "extra" instruktioner kan minskas.

Talaren undersöker ett dussintal av dem och visar både situationer där de hjälper till att påskynda programexekveringen, och de där dessa metoder inte är tillämpliga.

Således leder Roman Rusyaev eleverna till slutsatsen att kod som innehåller undantagshantering inte alltid kan exekveras med noll overhead, och ger följande råd:

  • när man utvecklar bibliotek är det värt att i princip överge undantag;
  • om undantag fortfarande behövs är det när det är möjligt värt att lägga till noexcept (och const) modifierare överallt så att kompilatorn kan optimera så mycket som möjligt.

I allmänhet bekräftade talaren uppfattningen att undantag bäst används till ett minimum eller överges helt.

Rapportbilderna finns tillgängliga på följande länk: ["C++ undantag genom linsen av LLVM-kompilatoroptimeringar"]

Generatorer, koroutiner och annan hjärnans sötma, Adi Shavit

C++ Ryssland: hur det hände
Skjut från presentationer

En av de många rapporterna på denna konferens som ägnades åt innovationer i C++20 var minnesvärd inte bara för dess färgglada presentation, utan också för dess tydliga identifiering av befintliga problem med insamlingsbehandlingslogiken (för loop, callbacks).

Adi Shavit lyfter fram följande: de för närvarande tillgängliga metoderna går igenom hela samlingen och ger inte tillgång till något internt mellanläge (eller de gör det vid återuppringningar, men med ett stort antal obehagliga biverkningar, som Callback Hell) . Det verkar som att det finns iteratorer, men även med dem är allt inte så smidigt: det finns inga gemensamma ingångs- och utgångspunkter (start → slut kontra rbegin → rend och så vidare), det är inte klart hur länge vi kommer att iterera? Från och med C++20 är dessa problem lösta!

Första alternativet: intervall. Genom att linda iteratorer får vi ett gemensamt gränssnitt för början och slutet av en iteration, och vi får även möjlighet att komponera. Allt detta gör det enkelt att bygga fullfjädrade databehandlingspipelines. Men allt är inte så smidigt: en del av beräkningslogiken finns i implementeringen av en specifik iterator, vilket kan komplicera koden att förstå och felsöka.

C++ Ryssland: hur det hände
Skjut från presentationer

Tja, för det här fallet lade C++20 till koroutiner (funktioner vars beteende liknar generatorer i Python): exekvering kan skjutas upp genom att returnera något aktuellt värde samtidigt som ett mellantillstånd bevaras. På så sätt uppnår vi inte bara att arbeta med data som den ser ut, utan också att kapsla in all logik i en specifik koroutin.

Men det finns en fluga i salvan: för tillfället stöds de bara delvis av befintliga kompilatorer, och är inte heller implementerade så snyggt som vi skulle vilja: till exempel är det ännu inte värt att använda referenser och tillfälliga objekt i koroutiner. Dessutom finns det vissa begränsningar för vad som kan vara koroutiner, och constexpr-funktioner, konstruktörer/destruktörer och main ingår inte i den här listan.

Coroutiner löser alltså en betydande del av problemen med enkelheten i databehandlingslogik, men deras nuvarande implementeringar kräver förbättringar.

material:

C++-trick från Yandex.Taxi, Anton Polukhin

I mina yrkesaktiviteter måste jag ibland implementera rent extra saker: ett omslag mellan det interna gränssnittet och API:et för något bibliotek, loggning eller analys. I det här fallet finns det vanligtvis inget behov av någon ytterligare optimering. Men vad händer om dessa komponenter används i några av de mest populära tjänsterna på RuNet? I en sådan situation måste du bara bearbeta terabyte per timme av loggar! Då räknas varje millisekund och därför måste man ta till olika knep – Anton Polukhin pratade om dem.

Det kanske mest intressanta exemplet var implementeringen av mönstret pekare-till-implementering (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 det här exemplet vill jag först bli av med rubrikfilerna för externa bibliotek - detta kommer att kompileras snabbare, och du kan skydda dig från möjliga namnkonflikter och andra liknande fel. 

Okej, vi flyttade #include till .cpp-filen: vi behöver en forward-declaration av det inkapslade API:et, såväl som std::unique_ptr. Nu har vi dynamiska tilldelningar och andra obehagliga saker som data utspridda över en massa data och reducerade garantier. std::aligned_storage kan hjälpa till med allt detta. 

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 enda problemet: du måste ange storlek och justering för varje omslag - låt oss göra vår finnemall med parametrar , använd några godtyckliga värden och lägg till en kontroll i förstöraren att vi har fått allt rätt: 

~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"
    ); 
}

Eftersom T redan är definierat vid bearbetning av destruktorn kommer denna kod att tolkas korrekt och i kompileringsstadiet kommer den att mata ut de erforderliga storleks- och inriktningsvärdena som måste anges som fel. Till priset av ytterligare en kompileringskörning blir vi alltså av med den dynamiska allokeringen av inslagna klasser, gömmer API:et i en .cpp-fil med implementeringen och får även en design som är mer lämpad för cachning av processorn.

Loggning och analys verkade mindre imponerande och kommer därför inte att nämnas i denna recension.

Rapportbilderna finns tillgängliga på följande länk: ["C++-tricks från Taxi"]

Moderna tekniker för att hålla din kod TORR, Björn Fahller

I det här föredraget visar Björn Fahller flera olika sätt att bekämpa stilfelet med upprepade tillståndskontroller:

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

Låter bekant? Genom att använda flera kraftfulla C++-tekniker som introducerats i de senaste standarderna kan du på ett elegant sätt implementera samma funktionalitet utan prestationsstraff. Jämföra:   

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

För att hantera ett obestämt antal kontroller måste du omedelbart använda olika mallar och vikningsuttryck. Låt oss anta att vi vill kontrollera likheten mellan flera variabler och enumens element tillståndstyp. Det första som kommer att tänka på är att skriva en hjälpfunktion är_någon_av:


enum state_type { IDLE, CONNECTED, DISCONNECTED };

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

Detta mellanresultat är en besvikelse. Än så länge har koden inte blivit mer läsbar:

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

Icke-typ mallparametrar hjälper till att förbättra situationen lite. Med deras hjälp kommer vi att överföra de uppräknade elementen i enumet till listan med mallparametrar: 

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

Genom att använda auto i en icke-typ-mallparameter (C++17), generaliserar metoden helt enkelt till jämförelser inte bara med state_type-element, utan också med primitiva typer som kan användas som icke-typ-mallparametrar:


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

Genom dessa successiva förbättringar uppnås den önskade flytande syntaxen för kontroller:


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 det här exemplet tjänar deduktionsguiden till att föreslå de önskade strukturmallparametrarna för kompilatorn, som känner till typerna av konstruktorargumenten. 

Vidare - mer intressant. Björn lär ut hur man generaliserar den resulterande koden för jämförelseoperatorer bortom ==, och sedan för godtyckliga operationer. Längs vägen förklaras sådana funktioner som no_unique_address-attribut (C++20) och mallparametrar i lambda-funktioner (C++20) med hjälp av exempel på användning. (Ja, nu är lambdasyntaxen ännu lättare att komma ihåg - det här är fyra på varandra följande par av parenteser av alla slag.) Den slutliga lösningen med funktioner som konstruktörsdetaljer värmer verkligen min själ, för att inte tala om uttrycket tupel i lambdas bästa traditioner kalkyl.

I slutet, glöm inte att putsa upp det:

  • Kom ihåg att lambdas är constexpr gratis; 
  • Låt oss lägga till perfekt vidarebefordran och titta på dess fula syntax i förhållande till parameterpaketet i lambda-stängningen;
  • Låt oss ge kompilatorn fler möjligheter till optimeringar med villkorlig noexcept; 
  • Låt oss ta hand om mer förståeliga felutdata i mallar tack vare explicita returvärden för lambdas. Detta kommer att tvinga kompilatorn att göra fler kontroller innan mallfunktionen faktiskt anropas - vid typkontrollstadiet. 

För detaljer, se föreläsningsmaterialet: 

Våra intryck

Vårt första deltagande i C++ Ryssland var minnesvärt för sin intensitet. Jag fick intrycket av C++ Ryssland som en uppriktig händelse, där gränsen mellan träning och direktkommunikation nästan är omärklig. Allt, från talarnas humör till tävlingarna från evenemangets partner, bidrar till heta diskussioner. Innehållet i konferensen, som består av rapporter, täcker ett ganska brett spektrum av ämnen inklusive C++ innovationer, fallstudier av stora projekt och ideologiska arkitektoniska överväganden. Men det skulle vara orättvist att ignorera den sociala komponenten i händelsen, som hjälper till att övervinna språkbarriärer inte bara i förhållande till C++.

Vi tackar konferensarrangörerna för möjligheten att delta i ett sådant evenemang!
Du kanske har sett arrangörernas inlägg om det förflutna, nuet och framtiden för C++ Ryssland på JUG Ru-bloggen.

Tack för att du läste, och vi hoppas att vår återberättelse av händelser var till hjälp!

Källa: will.com

Lägg en kommentar