C++ روسیه: چگونه اتفاق افتاد

اگر در ابتدای نمایش بگویید که کد ++C روی دیوار آویزان است، در پایان باید به پای شما شلیک کند.

بیارن استروستروپ

از 31 اکتبر تا 1 نوامبر، کنفرانس C++ Russia Piter در سن پترزبورگ برگزار شد - یکی از کنفرانس های برنامه نویسی در مقیاس بزرگ در روسیه، که توسط گروه JUG Ru سازماندهی شد. سخنرانان مهمان شامل اعضای کمیته استانداردهای C++، سخنرانان CppCon، نویسندگان کتاب O'Reilly، و نگهبانان پروژه هایی مانند LLVM، libc++ و Boost هستند. هدف این کنفرانس توسعه دهندگان با تجربه ++C است که می خواهند تخصص خود را عمیق تر کنند و تجربیات خود را در ارتباط زنده تبادل کنند. به دانشجویان، دانشجویان تحصیلات تکمیلی و معلمان دانشگاه ها تخفیف های بسیار خوبی ارائه می شود.

نسخه مسکو کنفرانس در اوایل آوریل سال آینده برای بازدید در دسترس خواهد بود، اما در این بین دانش آموزان ما به شما خواهند گفت که در آخرین رویداد چه چیزهای جالبی یاد گرفته اند. 

C++ روسیه: چگونه اتفاق افتاد

عکس از آلبوم کنفرانس

درباره ما

دو دانشجو از دانشکده عالی اقتصاد دانشگاه تحقیقات ملی - سن پترزبورگ روی این پست کار کردند:

  • لیزا واسیلنکو یک دانشجوی سال چهارم کارشناسی است که در رشته زبان های برنامه نویسی به عنوان بخشی از برنامه ریاضیات کاربردی و علوم کامپیوتر تحصیل می کند. با آشنایی با زبان C++ در سال اول دانشگاه، متعاقباً از طریق کارآموزی در صنعت، تجربه کار با آن را به دست آوردم. اشتیاق من به زبان های برنامه نویسی به طور کلی و برنامه نویسی کاربردی به طور خاص اثر خود را در انتخاب گزارش های کنفرانس گذاشت.
  • دانیا اسمیرنوف دانشجوی سال اول برنامه کارشناسی ارشد "برنامه نویسی و تجزیه و تحلیل داده ها" است. وقتی هنوز در مدرسه بودم، مسائل المپیاد را به زبان C++ می نوشتم و بعد به نحوی اتفاق افتاد که این زبان دائماً در فعالیت های آموزشی مطرح می شد و در نهایت به زبان اصلی کار تبدیل شد. تصمیم گرفتم در کنفرانس شرکت کنم تا دانش خود را افزایش دهم و همچنین در مورد فرصت های جدید بیاموزم.

در خبرنامه، رهبری دانشکده اغلب اطلاعات مربوط به رویدادهای آموزشی مرتبط با تخصص ما را به اشتراک می گذارد. در ماه سپتامبر اطلاعاتی در مورد C++ روسیه دیدیم و تصمیم گرفتیم به عنوان شنونده ثبت نام کنیم. این اولین تجربه ما از شرکت در چنین کنفرانس هایی است.

ساختار کنفرانس

  • گزارش ها

در طول دو روز، کارشناسان 30 گزارش را مطالعه کردند که موضوعات داغ بسیاری را پوشش می‌داد: استفاده هوشمندانه از ویژگی‌های زبان برای حل مشکلات کاربردی، به‌روزرسانی‌های زبان آتی در ارتباط با استاندارد جدید، سازش در طراحی C++ و اقدامات احتیاطی هنگام کار با پیامدهای آن، مثال‌ها. معماری جالب پروژه، و همچنین برخی از جزئیات زیرساخت زبان. سه اجرا به طور همزمان اجرا شد، اغلب دو اجرا به زبان روسی و یکی به زبان انگلیسی.

  • مناطق بحث و گفتگو

پس از سخنرانی، تمامی سوالات مطرح نشده و بحث های ناتمام به قسمت های اختصاصی برای ارتباط با سخنرانان مجهز به تابلوهای نشانگر منتقل شد. یک راه خوب برای دور کردن فاصله بین سخنرانی ها با یک مکالمه دلپذیر.

  • گفتگوهای رعد و برق و بحث های غیررسمی

اگر می‌خواهید گزارش کوتاهی ارائه کنید، می‌توانید برای سخنرانی رعد و برق عصر روی تخته سفید ثبت‌نام کنید و پنج دقیقه وقت داشته باشید تا در مورد هر موضوعی در مورد موضوع کنفرانس صحبت کنید. برای مثال، معرفی سریع ضدعفونی‌کننده‌ها برای C++ (برای برخی جدید بود) یا داستانی در مورد یک اشکال در تولید موج سینوسی که فقط شنیده می‌شود، اما دیده نمی‌شود.

قالب دیگر، بحث میزگرد «با کمیته قلبی به قلب» است. روی صحنه برخی از اعضای کمیته استانداردسازی هستند، روی پروژکتور یک شومینه قرار دارد (رسما - برای ایجاد یک فضای صمیمانه، اما دلیل "چون همه چیز در آتش است" خنده دار تر به نظر می رسد)، سوالاتی در مورد استاندارد و دیدگاه کلی C ++ ، بدون بحث های فنی داغ و هالیوار. معلوم شد که این کمیته همچنین شامل افراد زنده ای است که ممکن است از چیزی کاملاً مطمئن نباشند یا چیزی را ندانند.

برای طرفداران هولیوارها، رویداد سوم روی پرونده باقی ماند - جلسه BOF "Go vs. C++". یک عاشق Go، یک عاشق C++ را می گیریم، قبل از شروع جلسه با هم 100500 اسلاید در مورد یک موضوع (مثل مشکلات بسته ها در C++ یا عدم وجود ژنریک در Go) آماده می کنند و سپس بحثی پر جنب و جوش بین خود دارند و با مخاطب، و مخاطب سعی می کند دو دیدگاه را همزمان درک کند. اگر هولیوار خارج از زمینه شروع شود، ناظم مداخله می کند و طرفین را آشتی می دهد. این قالب اعتیادآور است: چند ساعت پس از شروع، فقط نیمی از اسلایدها تکمیل شد. پایان باید بسیار تسریع می شد.

  • شریک می ایستد

شرکای کنفرانس در سالن ها حضور داشتند - در غرفه ها در مورد پروژه های جاری صحبت کردند، کارآموزی و اشتغال را ارائه کردند، آزمون ها و مسابقات کوچک برگزار کردند و همچنین جوایز خوبی را قرعه کشی کردند. در همان زمان، برخی از شرکت ها حتی پیشنهاد دادند که مراحل اولیه مصاحبه را طی کنند، که می تواند برای کسانی که نه تنها برای شنیدن گزارش ها آمده اند مفید باشد.

جزئیات فنی گزارش ها

ما هر دو روز به گزارش ها گوش دادیم. گاهی اوقات انتخاب یک گزارش از گزارش های موازی دشوار بود - ما توافق کردیم که از هم جدا شویم و دانش به دست آمده در زمان استراحت را مبادله کنیم. و با این وجود، به نظر می رسد که خیلی چیزها از قلم افتاده است. در اینجا می‌خواهیم در مورد محتوای برخی از گزارش‌هایی که به نظر ما جالب‌تر بودند، صحبت کنیم

استثناها در C++ از طریق منشور بهینه سازی کامپایلر، رومن روسایف

C++ روسیه: چگونه اتفاق افتاد
اسلاید از презентации

همانطور که از عنوان پیداست، رومن به کار با استثناها با استفاده از LLVM به عنوان مثال نگاه کرد. در عین حال، برای کسانی که از Clang در کار خود استفاده نمی کنند، این گزارش هنوز هم می تواند ایده ای از نحوه بهینه سازی کد به طور بالقوه ارائه دهد. این به این دلیل است که توسعه دهندگان کامپایلرها و کتابخانه های استاندارد مربوطه با یکدیگر ارتباط برقرار می کنند و بسیاری از راه حل های موفق می توانند همزمان باشند.

بنابراین، برای رسیدگی به یک استثنا، باید کارهای زیادی انجام دهید: کد مدیریت (در صورت وجود) یا منابع رایگان را در سطح فعلی فراخوانی کنید و پشته را بالاتر ببرید. همه اینها به این واقعیت منجر می شود که کامپایلر دستورالعمل های اضافی را برای فراخوانی هایی که به طور بالقوه استثناهایی ایجاد می کنند اضافه می کند. بنابراین، اگر استثنا واقعاً مطرح نشود، برنامه همچنان اقدامات غیر ضروری را انجام می دهد. به منظور کاهش سربار، LLVM چندین روش اکتشافی برای تعیین موقعیت‌هایی دارد که نیازی به افزودن کد مدیریت استثنا نیست یا می‌توان تعداد دستورالعمل‌های «اضافی» را کاهش داد.

گوینده حدود ده مورد از آنها را بررسی می کند و هم موقعیت هایی را نشان می دهد که در آن به سرعت اجرای برنامه کمک می کنند و هم موقعیت هایی را که این روش ها قابل اجرا نیستند.

بنابراین، رومن روسایف دانش‌آموزان را به این نتیجه می‌رساند که کدهای حاوی مدیریت استثنا همیشه نمی‌توانند با سربار صفر اجرا شوند و توصیه‌های زیر را ارائه می‌دهد:

  • هنگام توسعه کتابخانه ها، اصولاً ارزش دارد که استثناها را کنار بگذاریم.
  • اگر هنوز استثناها مورد نیاز هستند، هر زمان که ممکن است ارزش آن را دارد که اصلاح کننده های noexcept (و const) را در همه جا اضافه کنید تا کامپایلر بتواند تا حد امکان بهینه سازی کند.

به طور کلی، سخنران این دیدگاه را تأیید کرد که استثناها بهتر است به حداقل ممکن استفاده شوند یا به طور کلی کنار گذاشته شوند.

اسلایدهای گزارش در لینک زیر موجود است: ["استثناهای C++ از طریق لنز بهینه سازی کامپایلر LLVM"]

ژنراتورها، کوروتین‌ها و دیگر شیرینی‌های ذهن‌ساز، آدی شاویت

C++ روسیه: چگونه اتفاق افتاد
اسلاید از презентации

یکی از گزارش‌های متعدد در این کنفرانس که به نوآوری‌های C++20 اختصاص داشت، نه تنها به دلیل ارائه رنگارنگ آن، بلکه به دلیل شناسایی واضح مشکلات موجود در منطق پردازش مجموعه (برای حلقه، برگشت‌های تماس) به یاد ماندنی بود.

آدی شاویت موارد زیر را برجسته می‌کند: روش‌های موجود در حال حاضر از کل مجموعه عبور می‌کنند و دسترسی به برخی از حالت‌های میانی داخلی را فراهم نمی‌کنند (یا در مورد کال‌بک‌ها انجام می‌دهند، اما با تعداد زیادی از عوارض جانبی ناخوشایند، مانند Callback Hell) . به نظر می رسد تکرار کننده وجود دارد، اما حتی با آنها همه چیز چندان هموار نیست: هیچ نقطه ورودی و خروجی مشترکی وجود ندارد (شروع → پایان در مقابل rbegin → rend و غیره)، مشخص نیست که تا چه زمانی تکرار خواهیم کرد؟ با شروع C++20، این مشکلات حل می شود!

گزینه اول: محدوده ها. با بسته بندی تکرار کننده ها، یک رابط مشترک برای شروع و پایان یک تکرار به دست می آوریم و همچنین توانایی نوشتن را نیز به دست می آوریم. همه اینها ساخت خطوط لوله پردازش داده کامل را آسان می کند. اما همه چیز چندان هموار نیست: بخشی از منطق محاسبات در داخل اجرای یک تکرار کننده خاص قرار دارد که می تواند کد را برای درک و اشکال زدایی پیچیده کند.

C++ روسیه: چگونه اتفاق افتاد
اسلاید از презентации

خوب، برای این مورد، C++20 روال‌هایی را اضافه کرد (توابعی که رفتار آنها شبیه ژنراتورهای پایتون است): اجرا را می‌توان با برگرداندن مقداری فعلی و در عین حال حفظ یک حالت میانی به تعویق انداخت. بنابراین، ما نه تنها به کار با داده‌ها آنطور که به نظر می‌رسد، می‌رسیم، بلکه تمام منطق را در داخل یک کوروتین خاص کپسوله می‌کنیم.

اما یک مگس در پماد وجود دارد: در حال حاضر آنها فقط تا حدی توسط کامپایلرهای موجود پشتیبانی می شوند و همچنین به همان شکلی که ما می خواهیم پیاده سازی نمی شوند: به عنوان مثال، هنوز ارزش استفاده از منابع و اشیاء موقت در برنامه ها را ندارد. به‌علاوه، محدودیت‌هایی در مورد اینکه چه چیزی می‌تواند کوروتین باشد وجود دارد، و توابع constexpr، سازنده/تخریب‌کننده و اصلی در این فهرست گنجانده نشده‌اند.

بنابراین، روال‌ها بخش قابل توجهی از مشکلات را با سادگی منطق پردازش داده‌ها حل می‌کنند، اما پیاده‌سازی فعلی آن‌ها نیاز به بهبود دارد.

مواد:

ترفندهای C++ از Yandex.Taxi، Anton Polukhin

در فعالیت‌های حرفه‌ای‌ام، گاهی اوقات مجبور می‌شوم چیزهای کاملاً کمکی را پیاده‌سازی کنم: یک بسته‌بندی بین رابط داخلی و API برخی از کتابخانه‌ها، ورود به سیستم یا تجزیه. در این حالت معمولاً نیازی به بهینه سازی اضافی نیست. اما اگر این مؤلفه‌ها در برخی از محبوب‌ترین سرویس‌های RuNet استفاده شوند، چه می‌شود؟ در چنین شرایطی باید به تنهایی ترابایت در ساعت لاگ را پردازش کنید! سپس هر میلی ثانیه حساب می شود و بنابراین باید به ترفندهای مختلفی متوسل شوید - آنتون پولوخین در مورد آنها صحبت کرد.

شاید جالب ترین مثال، اجرای الگوی اشاره گر به پیاده سازی (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. منتقل کردیم: به یک اعلان رو به جلو از 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++ از تاکسی"]

تکنیک های مدرن برای خشک نگه داشتن کد شما، Björn Fahler

در این سخنرانی، Björn Fahler چندین راه مختلف برای مبارزه با نقص سبک بررسی مکرر شرایط را نشان می دهد:

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

آشنا بنظر رسیدن؟ با استفاده از چندین تکنیک قدرتمند C++ که در استانداردهای اخیر معرفی شده‌اند، می‌توانید همان عملکرد را بدون هیچ جریمه عملکردی به زیبایی اجرا کنید. مقایسه کنید:   

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

برای رسیدگی به تعداد نامشخصی از چک‌ها، باید فوراً از قالب‌های متغیر و عبارات فولد استفاده کنید. بیایید فرض کنیم که می‌خواهیم برابری چندین متغیر را با عنصر state_type enum بررسی کنیم. اولین چیزی که به ذهن می رسد نوشتن یک تابع کمکی 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)); 

پارامترهای قالب غیر از نوع به بهبود وضعیت کمی کمک می کند. با کمک آنها، عناصر قابل شمارش enum را به لیست پارامترهای الگو منتقل می کنیم: 

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

با استفاده از خودکار در یک پارامتر الگوی غیر نوع (C++17)، این رویکرد به سادگی به مقایسه‌ها نه تنها با عناصر state_type، بلکه با انواع ابتدایی که می‌توانند به عنوان پارامترهای قالب غیر از نوع استفاده شوند، تعمیم می‌دهد:


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

در این مثال، راهنمای کسر برای پیشنهاد پارامترهای قالب ساختاری مورد نظر به کامپایلر، که انواع آرگومان های سازنده را می شناسد، خدمت می کند. 

بیشتر - جالب تر. Bjorn نحوه تعمیم کد حاصل را برای عملگرهای مقایسه فراتر از == و سپس برای عملیات دلخواه آموزش می دهد. در طول مسیر، ویژگی‌هایی مانند ویژگی no_unique_address (C++20) و پارامترهای الگو در توابع لامبدا (C++20) با استفاده از مثال‌هایی از استفاده توضیح داده می‌شوند. (بله، اکنون به خاطر سپردن نحو لامبدا آسان‌تر است - اینها چهار جفت پرانتز متوالی از همه نوع هستند.) راه‌حل نهایی با استفاده از توابع به عنوان جزئیات سازنده واقعاً روح من را گرم می‌کند، بدون ذکر عبارت tuple در بهترین سنت‌های لامبدا. حساب دیفرانسیل و انتگرال

در پایان، صیقل دادن آن را فراموش نکنید:

  • به یاد داشته باشید که لامبداها به صورت رایگان constexpr هستند. 
  • بیایید ارسال کامل را اضافه کنیم و به نحو زشت آن در رابطه با بسته پارامتر در بسته شدن لامبدا نگاه کنیم.
  • اجازه دهید به کامپایلر فرصت های بیشتری برای بهینه سازی با noexcept شرطی بدهیم. 
  • بیایید به لطف مقادیر بازگشتی صریح لامبدا، از خروجی خطای قابل فهم تر در قالب ها مراقبت کنیم. این کار کامپایلر را مجبور می‌کند تا قبل از فراخوانی تابع قالب - در مرحله بررسی نوع، بررسی‌های بیشتری انجام دهد. 

برای جزئیات، لطفاً به مطالب سخنرانی مراجعه کنید: 

برداشت های ما

اولین حضور ما در C++ روسیه به خاطر شدت آن خاطره انگیز بود. من تصور C++ روسیه را به عنوان یک رویداد صمیمانه دریافت کردم، جایی که مرز بین آموزش و ارتباط زنده تقریباً نامحسوس است. همه چیز، از حال و هوای سخنرانان گرفته تا رقابت های شرکای رویداد، منجر به بحث های داغ می شود. محتوای کنفرانس، متشکل از گزارش‌ها، طیف نسبتاً گسترده‌ای از موضوعات از جمله نوآوری‌های ++C، مطالعات موردی پروژه‌های بزرگ و ملاحظات معماری ایدئولوژیک را پوشش می‌دهد. اما نادیده گرفتن مؤلفه اجتماعی رویداد، که به غلبه بر موانع زبان در رابطه نه تنها با C++ کمک می کند، ناعادلانه خواهد بود.

ما از برگزارکنندگان کنفرانس برای فرصت شرکت در چنین رویدادی تشکر می کنیم!
ممکن است پست برگزارکنندگان در مورد گذشته، حال و آینده C++ روسیه را دیده باشید در وبلاگ JUG Ru.

از خواندن متشکریم، و امیدواریم که بازگویی رویدادها مفید بوده باشد!

منبع: www.habr.com

اضافه کردن نظر