C++ 俄羅斯:它是如何發生的

如果一開始你就說牆上掛著C++程式碼,那麼到最後它一定會搬石頭砸你的腳。

比亞內·斯特魯斯特魯普

31月1日至XNUMX月XNUMX日,C++ Russia Piter大會在聖彼得堡舉行-俄羅斯大型程式會議之一,由JUG Ru集團主辦。演講嘉賓包括 C++ 標準委員會成員、CppCon 演講者、O'Reilly 書籍作者以及 LLVM、libc++ 和 Boost 等專案的維護者。該會議面向經驗豐富的 C++ 開發人員,希望在現場交流中加深專業知識並交流經驗。為學生、研究生和大學教師提供非常優惠的折扣。

莫斯科版的會議最早將於明年四月開放,但同時,我們的學生將告訴您他們在上一次活動中學到了什麼有趣的事情。 

C++ 俄羅斯:它是如何發生的

照片來自 會議相冊

關於我們

來自聖彼得堡國立研究大學高等經濟學院的兩名學生撰寫了這篇文章:

  • Liza Vasilenko 是一名四年級本科生,學習程式語言,作為應用數學和電腦科學課程的一部分。我在大學第一年就熟悉了 C++ 語言,然後透過業界實習獲得了使用它的經驗。對一般程式語言和特別是函數式程式設計的熱情在會議報告的選擇上留下了印記。
  • Danya Smirnov 是「程式設計與數據分析」碩士課程的一年級學生。當我還在學校的時候,我就用 C++ 寫了奧數題,然後不知怎的,這種語言不斷地出現在教育活動中,並最終成為主要的工作語言。我決定參加這次會議,以提高我的知識並了解新的機會。

在時事通訊中,教師領導者經常分享與我們專業相關的教育活動的資訊。九月份,我們看到了有關 C++ Russia 的信息,並決定註冊為聽眾。這是我們第一次參加此類會議。

會議結構

  • 報告

在兩天的時間裡,專家們閱讀了30 份報告,涵蓋了許多熱門話題:巧妙地利用語言特性來解決應用問題、即將與新標準相關的語言更新、C++ 設計中的妥協以及處理其後果時的預防措施、範例有趣的專案架構,以及語言基礎架構的一些底層細節。一次有 3 場表演,最常見的是兩場俄語表演和一場英語表演。

  • 討論區

演講結束後,所有未提出的問題和未完成的討論都被轉移到專門指定的區域與演講者交流,並配備了標記板。這是透過愉快的談話來打發演講間隙的好方法。

  • 閃電會談和非正式討論

如果您想做一個簡短的報告,您可以在白板上報名參加晚上的閃電演講,並獲得五分鐘的時間來談論有關會議主題的任何內容。例如,對 C++ 消毒劑的快速介紹(對某些人來說這是新的)或關於正弦波生成中只能聽到但看不到的錯誤的故事。

另一種形式是「與心連心委員會」小組討論。台上是標準化委員會的一些成員,投影機上是一個壁爐(官方是為了營造一種真誠的氛圍,但「因為一切都著火了」的原因似乎更有趣),關於標準和C++總體願景的問題,沒有激烈的技術討論和霍利戰爭。事實證明,委員會中還包括活著的人,他們可能對某事不完全確定,或者可能不知道某事。

對於 holivars 的粉絲來說,第三個活動仍然是案例 - BOF 會議“Go vs. C++”。我們帶了一個Go 愛好者,一個C++ 愛好者,在會議開始之前,他們一起準備了100500 張關於某個主題的幻燈片(例如C++ 中的包問題或Go 中缺少泛型),然後他們之間進行了熱烈的討論與觀眾一起,觀眾試圖同時理解兩種觀點。如果一場霍利瓦開始斷章取義,主持人就會介入並調解各方。這種格式很容易上癮:開始幾個小時後,投影片只完成了一半。結束必須大大加速。

  • 合作夥伴展位

會議的合作夥伴出席了會議大廳——在展位上他們談論了當前的項目,提供了實習和就業機會,舉辦了測驗和小型比賽,還抽出了精美的獎品。同時,一些公司甚至主動提出進行初期採訪,這對於那些不僅僅是來聽報告的人來說可能會很有用。

報告的技術細節

這兩天我們都聽取了報告。有時很難從平行報告中選擇一份報告 - 我們同意分開並交換在休息期間獲得的知識。即便如此,似乎還有很多東西被遺漏了。在這裡我們想談談一些我們認為最有趣的報告內容

從編譯器最佳化的角度看 C++ 中的異常,Roman Rusyaev

C++ 俄羅斯:它是如何發生的
幻燈片自 介紹

如標題所示,Roman 以 LLVM 為例研究如何處理異常。同時,對於那些在工作中不使用 Clang 的人來說,該報告仍然可以提供一些如何優化程式碼的想法。之所以如此,是因為編譯器和相應標準函式庫的開發人員相互溝通,許多成功的解決方案可以重疊。

因此,要處理異常,您需要做很多事情:呼叫處理程式碼(如果有)或釋放當前層級的資源並向上旋轉堆疊。所有這些導致編譯器為可能引發異常的呼叫添加額外的指令。因此,如果實際上沒有引發異常,程式仍然會執行不必要的操作。為了以某種方式減少開銷,LLVM 有幾種啟發式方法來確定不需要添加異常處理程式碼或可以減少「額外」指令數量的情況。

演講者檢查了大約十幾種方法,並展示了它們有助於加快程序執行的情況以及這些方法不適用的情況。

因此,Roman Rusyaev 引導學生得出這樣的結論:包含異常處理的程式碼不能總是以零開銷執行,並給予以下建議:

  • 在開發庫時,原則上放棄例外是值得的;
  • 如果仍然需要異常,那麼只要有可能,就值得在各處添加 noexcept (和 const)修飾符,以便編譯器可以盡可能地優化。

總的來說,發言者確認了這樣的觀點,即最好盡量減少例外情況或完全放棄例外情況。

報告幻燈片可透過以下連結取得: [“從 LLVM 編譯器優化的角度看 C++ 異常”]

生成器、協程和其他令人大腦舒展的甜蜜,Adi Shavit

C++ 俄羅斯:它是如何發生的
幻燈片自 介紹

本次會議上致力於C++20 創新的眾多報告中的一份令人難忘,不僅因為其豐富多彩的演示,而且還因為它清楚地識別了集合處理邏輯(for 循環、回調)中存在的問題。

Adi Shavit 強調了以下內容:目前可用的方法會遍歷整個集合,並且不提供對某些內部中間狀態的訪問(或者在回調的情況下提供訪問,但會帶來大量令人不快的副作用,例如Callback Hell) 。看起來似乎有迭代器,但即使有了它們,一切也不是那麼順利:沒有共同的入口點和出口點(begin → end 與 rbegin → rend 等等),不清楚我們將迭代多長時間?從C++20開始,這些問題都解決了!

第一個選項:範圍。透過包裝迭代器,我們獲得了迭代開始和結束的通用接口,並且還獲得了組合的能力。所有這些使得建立成熟的數據處理管道變得容易。但並非一切都那麼順利:部分計算邏輯位於特定迭代器的實作內部,這會使程式碼的理解和調試變得複雜。

C++ 俄羅斯:它是如何發生的
幻燈片自 介紹

好吧,對於這種情況,C++20 新增了協程(其行為類似於 Python 中的生成器的函數):可以透過傳回一些當前值來延遲執行,同時保留中間狀態。因此,我們不僅可以處理出現的數據,還可以將所有邏輯封裝在特定的協程中。

但美中不足的是:目前它們僅得到現有編譯器的部分支持,並且實現也沒有我們希望的那麼整齊:例如,還不值得在協程中使用引用和臨時對象。另外,對於協程的內容有一些限制,constexpr 函式、建構函式/析構函式和 main 不包含在這個清單中。

因此,協程透過資料處理邏輯的簡單性解決了很大一部分問題,但其當前的實作需要改進。

材料:

來自 Yandex.Taxi、Anton Polukhin 的 C++ 技巧

在我的專業活動中,有時我必須實作純粹的輔助性的東西:一些函式庫的內部介面和 API 之間的包裝器、日誌記錄或解析。在這種情況下,通常不需要任何額外的最佳化。但是,如果這些元件用於 RuNet 上一些最受歡迎的服務呢?在這種情況下,您每小時必須處理 TB 級的日誌!然後每一毫秒都很重要,因此你必須訴諸各種技巧 - Anton Polukhin 談到了它們。

也許最有趣的例子是指標到實現 (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 Fahller

在本次演講中,Björn Fahller 展示了幾種不同的方法來克服重複條件檢查的風格缺陷:

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

聽起來有點熟?透過使用最新標準中引入的多種強大的 C++ 技術,您可以優雅地實現相同的功能,而不會造成任何效能損失。比較:   

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

要處理不固定數量的檢查,您立即需要使用可變參數範本和折疊表達式。假設我們想要檢查幾個變數與列舉的 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)); 

非類型模板參數將有助於稍微改善這種情況。在他們的幫助下,我們將枚舉的可枚舉元素轉移到模板參數清單中: 

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

透過在非類型模板參數 (C++17) 中使用 auto,該方法簡單地推廣為不僅與 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) 和 lambda 函數 (C++20) 中的模板參數等功能。 (是的,現在lambda 語法更容易記住了- 這是四對連續的各種括號。)使用函數作為構造函數細節的最終解決方案確實溫暖了我的靈魂,更不用說lambda 最佳傳統中的表達式元組了結石。

最後,別忘了潤飾:

  • 請記住,lambda 是免費的 constexpr; 
  • 讓我們加入完美轉送並看看它與 lambda 閉包中的參數包相關的醜陋語法;
  • 讓我們透過條件 noexcept 給編譯器更多的最佳化機會; 
  • 由於 lambda 的明確傳回值,讓我們在模板中處理更易於理解的錯誤輸出。這將迫使編譯器在實際呼叫模板函數之前(在類型檢查階段)進行更多檢查。 

詳細內容請參考講座資料: 

我們的印象

我們第一次參加 C++ Russia 活動的強度令人難忘。 C++ Russia 給我的印像是真誠的活動,訓練和現場交流之間的界線幾乎難以察覺。從演講者的心情到活動夥伴的比賽,一切都有助於引發熱烈的討論。會議內容由報告組成,涵蓋了相當廣泛的主題,包括C++創新、大型專案案例研究和思想架構考量。但忽視該活動的社交成分是不公平的,它不僅有助於克服與 C++ 相關的語言障礙。

感謝會議主辦單位給予我們參加這樣的活動的機會!
您可能已經看過組織者關於 C++ Russia 的過去、現在和未來的帖子 在 JUG Ru 部落格上.

感謝您的閱讀,希望我們對事件的重述對您有幫助!

來源: www.habr.com

添加評論