C++ Belarus: як гэта было

Калі ў пачатку п'есы вы кажаце, што на сцяне вісіць код на З++, то да канца ён павінен абавязкова стрэліць вам у нагу.

Б'ярне Строуструп

З 31-га кастрычніка па 1-га лістапада ў Пецярбургу прайшла канферэнцыя C++ Russia Piter – адна з маштабных канферэнцый па праграмаванні ў Расіі, якая арганізуецца JUG Ru Group. Сярод запрошаных спікераў – сябры камітэта па стандартызацыі C++, дакладчыкі з CppCon, аўтары кніг выдавецтва O’Reilly, а таксама мэйнтэйнеры такіх праектаў, як LLVM, libc++ і Boost. Канферэнцыя арыентавана на вопытных распрацоўшчыкаў на C++, якія жадаюць паглыбіць сваю экспертызу і абмяняцца вопытам у жывых зносінах. Студэнтам, аспірантам і выкладчыкам універсітэтаў прадастаўляюцца вельмі прыемныя скідкі.

Маскоўскае выданне канферэнцыі можна будзе наведаць ужо ў красавіку наступнага года, а пакуль нашы студэнты раскажуць, што цікавага яны даведаліся на мерапрыемстве. 

C++ Belarus: як гэта было

Фота з альбома канферэнцыі

Пра нас

Над гэтай пасадай працавалі двое студэнтаў НДУ ВШЭ – Санкт-Пецярбург:

  • Ліза Васіленка - студэнтка 4-га курса бакалаўрыяту, якая вывучае напрамак "Мовы праграмавання" ў рамках праграмы "Прыкладная матэматыка і інфарматыка". Пазнаёміўшыся з мовай C++ на першым курсе ўніверсітэта, пасля набыла досвед працы з ім на стажыроўках у індустрыі. Захапленне мовамі праграмавання ў цэлым і функцыянальным праграмаваннем у прыватнасці наклала адбітак на выбар дакладаў на канферэнцыі.
  • Даня Смірноў - студэнт 1-га курса магістратуры "Праграмаванне і аналіз дадзеных". Яшчэ ў школе пісаў на C++ алімпіядныя задачы, а далей неяк так выйшла, што мова стала ўсплывала ў вучэбнай дзейнасці і ў выніку стала асноўным працоўным. У канферэнцыі вырашыў удзельнічаць, каб падцягнуць свае веды, а таксама даведацца пра новыя магчымасці.

У рассылцы кіраўніцтва факультэта часта дзеліцца інфармацыяй аб адукацыйных падзеях, звязаных з нашай спецыяльнасцю. У верасні мы ўбачылі інфармацыю аб C++ Belarus і вырашылі зарэгістравацца ў якасці слухачоў. Гэта – наш першы досвед удзелу ў падобных канферэнцыях.

Структура канферэнцыі

  • Даклады

На працягу двух дзён эксперты прачыталі 30 дакладаў, асвятліўшы шмат гарачых топікаў: дасціпныя прымянення фічаў мовы для вырашэння прыкладных задач, будучыя абнаўленні мовы ў сувязі з новым стандартам, кампрамісы пры дызайне C++ і меры засцярогі пры працы з іх наступствамі, прыклады цікавай архітэктуры праектаў, а таксама некаторыя падкапотныя дэталі інфраструктуры мовы. Адначасова праходзіла па 3 выступленні, часцей за ўсё два на рускай і адно на англійскай мове.

  • Discussion zones

Пасля выступлення ўсе незададзеныя пытанні і незавершаныя абмеркаванні пераносіліся ў спецыяльна выдзеленыя зоны зносін з дакладчыкамі, абсталяваныя маркернымі дошкамі. Добры спосаб прабавіць перапынак паміж выступамі за прыемнай гутаркай.

  • Lightning Talks і нефармальныя дыскусіі

Калі захацелася зрабіць кароткі даклад - можна запісацца на маркернай дошцы на вячэрні Lightning Talk і атрымаць пяць хвілін часу на аповяд аб чым заўгодна па тэме канферэнцыі. Напрыклад, хуткае ўвядзенне ў sanitizers для C++ (для некаторых аказалася ў навінку) або гісторыя пра баг у генерацыі сінусоіды, які можна толькі пачуць, але не ўбачыць.

Іншы фармат - панэльная дыскусія «З камітэтам па душах». На сцэне – некаторыя члены камітэта па стандартызацыі, на праектары – камін (афіцыйна – для стварэння душэўнай атмасферы, але прычына «таму што ЎСЁ Ў АГНЕ» здаецца пацешней), пытанні – пра стандарт і агульнае бачанне C++, без бурных тэхнічных абмеркаванняў і халівараў. Аказалася, што ў камітэце таксама сядзяць жывыя людзі, якія могуць быць у чымсьці не да канца ўпэўненыя ці чагосьці не ведаць.

Для аматараў халівараў па справе заставалася трэцяе мерапрыемства – BOF-сесія "Go супраць C++". Бярэм аматара Go, аматара C++, перад пачаткам сесіі яны разам рыхтуюць 100500 слайдаў на тэму (накшталт праблем з пакетамі ў C++ ці адсутнасцю джэнерыкаў у Go), а затым яны ажыўлена дыскутуюць паміж сабой і з залай, а зала спрабуе зразумець адразу два пункта гледжання. . Калі пачынаецца халівар не па справе - умешваецца мадэратар і прымірае бакі. Такі фармат зацягвае: праз некалькі гадзін пасля пачатку была пройдзена толькі палова слайдаў. Канец прыйшлося моцна паскараць.

  • Стэнды партнёраў

У холах былі прадстаўлены партнёры канферэнцыі — на стэндах расказвалі пра бягучыя праекты, прапаноўвалі стажыроўкі і працаўладкаванне, праводзілі квізы і невялікія спаборніцтвы, а таксама разыгрывалі прыемныя прызы. Пры гэтым некаторыя кампаніі нават прапаноўвалі прайсці пачатковыя этапы сумоўяў, што можа быць карысна для тых, хто прыехаў не толькі слухаць даклады.

Тэхнічныя падрабязнасці дакладаў

Мы слухалі даклады абодва дні. Сітавінай было цяжка абраць адзін даклад з раўналежна ідучых – мы дамовіліся падзяляцца і абменьвацца атрыманымі ведамі ў перапынках. І нават так, здаецца, што шмат што засталося ўпушчана. Тут мы хацелі б расказаць пра змест некаторых дакладаў, якія падаліся нам самымі цікавымі.

Выключэнні ў C++ праз прызму кампілятарных аптымізацый, Раман Русяеў

C++ Belarus: як гэта было
Слайд з прэзентацыі

Як зразумела з назвы, Раман разгледзеў працу з выключэннямі на прыкладзе LLVM. Пры гэтым для не выкарыстоўваюць у сваёй працы Clang даклад усё роўна можа даць некаторае ўяўленне аб тым, як код патэнцыйна можа быць аптымізаваны. Гэта так, таму што распрацоўшчыкі кампілятараў і адпаведных стандартных бібліятэк маюць зносіны паміж сабой і многія ўдалыя рашэнні могуць супадаць.

Такім чынам, для апрацоўкі выключэння патрабуецца зрабіць мноства дзеянняў: выклікаць код апрацоўкі (калі ёсць) ці вызваліць рэсурсы на бягучым узроўні і раскруціць стэк вышэй. Усё гэта вядзе да таго, што для патэнцыйна якія выдаюць выключэнні выклікаў кампілятар дадае дадатковыя інструкцыі. Таму калі выключэнне па факце не будзе выклікана, праграма ўсё роўна стане выконваць непатрэбныя дзеянні. Для таго, каб неяк знізіць накладныя выдаткі, у LLVM ёсць некалькі эўрыстык вызначэння сітуацый, дзе код апрацоўкі выключэнняў дадаваць не трэба ці можна знізіць колькасць "лішніх" інструкцый.

Дакладчык разглядае каля дзясятка з іх і паказвае як сітуацыі, дзе яны дапамагаюць паскорыць выкананне праграмы, так і тыя, дзе дадзеныя метады не дастасавальныя.

Такім чынам, Раман Русяеў падводзіць слухачоў да высновы, што код, які змяшчае працу з выключэннямі, далёка не заўсёды можна выконваць з нулявымі накладнымі выдаткамі, і дае наступныя парады:

  • пры распрацоўцы бібліятэк трэба адмовіцца ад выключэнняў у прынцыпе;
  • калі выключэнні ўсё ж патрэбныя, то па магчымасці ўсюды варта дадаваць мадыфікатары noexcept (і const), каб кампілятар мог саптымізаваць як мага больш.

У цэлым, дакладчык пацвердзіў меркаванне, што выключэнні лепш за ўсё выкарыстоўваць па мінімуме ці ўвогуле ад іх адмовіцца.

Слайды даклада даступныя па спасылцы: [«Выключэнні C++ праз прызму кампілятарных аптымізацый LLVM»]

Generators, coroutines and iншых brain-unrolling sweetness, Adi Shavit

C++ Belarus: як гэта было
Слайд з прэзентацыі

Адзін са шматлікіх дакладаў гэтай канферэнцыі, прысвечаных новаўвядзенням C++20, запомніўся не толькі маляўніча аформленай прэзентацыяй, але і выразным пазначэннем наяўных праблем з логікай апрацоўкі калекцый (цыкл for, callback-і).

Adi Shavit вылучае наступныя: наяўныя цяпер метады праходзяць калекцыю цалкам і пры гэтым не даюць доступу да некаторага ўнутранага прамежкавага стану (або даюць у выпадку callback-ов, але з вялікай колькасцю непрыемных пабочных эфектаў, тыпу таго ж Callback Hell). Здавалася б, ёсць ітэратары, але і з імі ўсё не так гладка: няма агульных кропкі ўваходу і выхаду (begin → end супраць rbegin → rend і гэтак далей), незразумела, колькі ўвогуле мы будзем ітэравацца? Пачынальна з C++20 дадзеныя праблемы вырашаюцца!

Першы варыянт: ranges. За кошт абгорткі па-над ітэратарамі мы атрымліваем агульны інтэрфейс для пачатку і канца ітэрацыі, а таксама атрымліваем магчымасць кампазіцыі. Усё гэта дазваляе лёгка будаваць паўнавартасныя канвееры апрацоўкі дадзеных. Але не ўсё так гладка: частка логікі вылічэнняў знаходзіцца ўнутры рэалізацыі канкрэтнага ітэратара, што можа ўскладніць код для ўспрымання і адладкі.

C++ Belarus: як гэта было
Слайд з прэзентацыі

Што ж, на гэты выпадак у C++20 дададзены каруціны (функцыі, паводзіны якіх падобна на генератары ў мове Python): выкананне можна адкласці, вярнуўшы некаторае бягучае значэнне з захаваннем пры гэтым прамежкавага стану. Такім чынам, мы дасягаем не толькі працы з дадзенымі па меры іх з'яўлення, але і инкапсулируем усю логіку ўсярэдзіне канкрэтнай каруціны.

Але ёсць лыжка дзёгцю: на дадзены момант яны толькі часткова падтрымліваюцца наяўнымі кампілятарамі, а таксама рэалізаваны не так акуратна, як хацелася б: напрыклад, пакуль не варта выкарыстоўваць у каруцінах спасылкі і часовыя аб'екты. Плюс, ёсць некаторыя абмежаванні па тым, што можа быць каруцінамі, і constexpr-функцыі, канструктары/дэструктары, а таксама main у гэты спіс не ўваходзяць.

Такім чынам, каруціны вырашаюць прыкметную частку праблем з прастатой логікі апрацоўкі дадзеных, але іх бягучыя рэалізацыі патрабуюць дапрацоўкі.

матэрыялы:

C++ трукі з Яндэкс.Таксі, Антон Палухін

У сваёй прафесійнай дзейнасці часам даводзіцца рэалізоўваць чыста дапаможныя штукі: абгортку паміж унутраным інтэрфейсам і API нейкай бібліятэкі, лагіраванне або парсінг. Пры гэтым звычайна няма неабходнасці ў нейкай дадатковай аптымізацыі. Але што, калі гэтыя кампаненты выкарыстоўваюцца ў адных з самых папулярных у Рунэце сэрвісах? У такой сітуацыі давядзецца апрацоўваць тэрабайты ў гадзіну адных толькі логаў! Тады кожная мілісекунда на рахунку і таму даводзіцца звяртацца да розных трукаў — пра іх і расказваў Антон Палухін.

Мабыць, самым цікавым прыкладам была рэалізацыя патэрна 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_; 
};

У дадзеным прыкладзе спачатку жадаецца пазбавіцца ад загалоўкавых файлаў вонкавых бібліятэк — так і кампілявацца будзе хутчэй, і можна засцерагчы сябе ад магчымых канфліктаў імёнаў і іншых падобных памылак. 

Добра, перанеслі #include у .cpp-файл: патрэбен forward-declaration абгорнутага API, а таксама std::unique_ptr. Цяпер у нас дынамічныя алакацыі і іншыя непрыемныя рэчы накшталт раскіданых па кучы дадзеных і паніжаных гарантый. З усім гэтым можа дапамагчы 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<kImplSize, kImplAlign> data_; 
};

Адзіная праблема: трэба для кожнай абгорткі прапісваць памер і выраўноўванне - зробім наш pimpl шаблонным з параметрамі , выкарыстоўваем з якімі-небудзь адвольнымі значэнні і дадамо ў дэструктар праверку, што мы ўсё адгадалі: 

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

Бо пры апрацоўцы дэструктара T ужо вызначаны, дадзены код будзе разбірацца карэктна і на стадыі кампіляцыі ў выглядзе памылак выведзе патрэбныя значэнні памеру і выраўноўванні, якія трэба ўпісаць. Такім чынам, коштам аднаго дадатковага запуску кампіляцыі мы пазбаўляемся ад дынамічнай алакацыі паварочваных класаў, хаваем API у .cpp-файл з рэалізацыяй, а таксама атрымліваем больш прыдатную для кэшавання працэсарам канструкцыю.

Лагіраванне і парсінг падаліся меней уражлівымі, а таму ў дадзеным аглядзе згаданыя не будуць.

Слайды даклада даступныя па спасылцы: [«C++ трукі з Таксі»]

Матэрыялы для мадэрнізацыі тэхналогій для DRY, Björn Fahller

У гэтым дакладзе Björn Fahller паказвае некалькі розных спосабаў барацьбы з такім стылістычным недахопам, як паўтараюцца праверкі ўмоў:

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

Знаёма? Выкарыстоўваючы некалькі магутных тэхнік З++, якія з'явіліся ў нядаўніх стандартах, можна хупава рэалізаваць тую ж функцыянальнасць без найменшых страт прадукцыйнасці. Параўнайце:   

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

Для апрацоўкі нефіксаванага ліку праверак адразу просіцца выкарыстоўваць variadic templates і fold expressions. Выкажам здагадку, што мы жадаем праверыць роўнасць некалькіх зменных элементу enum’a state_type. Першае, што прыходзіць на розум - напісаць дапаможную функцыю is_any_of:


enum state_type { IDLE, CONNECTED, DISCONNECTED };

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

Такі прамежкавы вынік выклікае расчараванне. Пакуль што код чытаемей не становіцца:

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

Трохі паправіць сітуацыю дапамогуць non-type template parameters. З іх дапамогай перанясём пералічаныя элементы enum’a у спіс параметраў шаблону: 

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

З выкарыстаннем auto ў не тыпавым параметры шаблону (C++17), падыход проста абагульняецца на параўнанні не толькі з элементамі state_type, але і з прымітыўнымі тыпамі, якія можна выкарыстаць у якасці non-type template parameters:


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

Шляхам такіх паслядоўных паляпшэнняў дасягаецца жаданы збеглы сінтаксіс для праверак:


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);

У гэтым прыкладзе deduction guide служыць для падказкі жаданых шаблонных параметраў структуры кампілятару, дасведчанаму тыпы аргументаў канструктара. 

Далей - цікавей. Б'ёрн вучыць абагульняць атрыманы код для аператараў параўнання апроч ==, а затым і для адвольных аперацый. Адначасна на прыкладзе выкарыстання тлумачацца такія фічы як no_unique_address attribute (C++20) і шаблонныя параметры ў лямбда-функцыях (C++20). (Так, зараз сінтакс лямбд яшчэ лягчэй запомніць гэта чатыры паслядоўныя пары дужак усіх гатункаў.) Выніковае рашэнне з выкарыстаннем функцый як дэталек канструктара асабіста мне вельмі грэе душу, не кажучы ўжо пра выраз tuple у лепшых традыцыях лямбда-вылічэнні.

У канцы не забываем навесці глянец:

  • Успомнім, што лямбды - constexpr за бясплатна; 
  • Дадамо perfect forwarding і паглядзім на яго выродлівы сінтакс у дачыненні да parameter pack у замыканні лямбд;
  • Дамо кампілятару больш магчымасцяў для аптымізацый з conditional noexcept; 
  • Паклапоцімся пра больш зразумелую выснову памылак у шаблонах дзякуючы відавочным якія вяртаюцца значэнням лямбд. Гэта прымусіць кампілятар рабіць больш праверак да ўласна выкліку шаблоннай функцыі - на стадыі праверкі тыпаў. 

Па падрабязнасці звяртайцеся да матэрыялаў лекцыі: 

Нашы ўражанні

Наш першы ўдзел у C++ Russia запомнілася сваёй насычанасцю. Склалася ўражанне аб З++ Russia як аб душэўным мерапрыемстве, дзе грань паміж навучаннем і жывымі зносінамі амаль не адчувальная. Усё, ад настрою дакладчыкаў да конкурсаў ад партнёраў мерапрыемства, размяшчае да бурных абмеркаванняў. Змястоўная частка канферэнцыі, якая складаецца ў дакладах, ахоплівае даволі шырокі спектр тэм уключаючы новаўвядзенні З++, прыклады з практыкі буйных праектаў і ідэалагічныя архітэктурныя меркаванні. Але было б несправядліва абдзяліць увагай і сацыяльны складнік мерапрыемства, якая спрыяе пераадоленню моўных бар'ераў у стаўленні не толькі З++.

Дзякуем арганізатарам канферэнцыі за магчымасць паўдзельнічаць у такой падзеі!
Пост арганізатараў аб мінулым, сучаснасці і будучыні C++ Belarus вы маглі бачыць у блогу JUG Ru.

Дзякуй за чытанне, і спадзяемся, што наш пераказ падзей аказаўся карысным!

Крыніца: habr.com

Дадаць каментар