C++ Nga: nó đã xảy ra như thế nào

Nếu lúc bắt đầu vở kịch bạn nói rằng có mã C++ treo trên tường, thì đến cuối vở kịch, nó chắc chắn sẽ bắn vào chân bạn.

Bjarne Stroustrup

Từ ngày 31/1 đến ngày XNUMX/XNUMX, hội nghị C++ Russia Piter đã được tổ chức tại St. Petersburg - một trong những hội nghị lập trình quy mô lớn ở Nga, do JUG Ru Group tổ chức. Các diễn giả được mời bao gồm các thành viên của Ủy ban Tiêu chuẩn C++, diễn giả CppCon, tác giả sách O'Reilly và người duy trì các dự án như LLVM, libc++ và Boost. Hội nghị hướng tới các nhà phát triển C++ có kinh nghiệm muốn nâng cao chuyên môn và trao đổi kinh nghiệm trong giao tiếp trực tiếp. Sinh viên, nghiên cứu sinh và giáo viên đại học được giảm giá rất tốt.

Phiên bản Moscow của hội nghị sẽ có mặt để tham quan sớm nhất là vào tháng XNUMX năm sau, nhưng trong thời gian chờ đợi, các sinh viên của chúng tôi sẽ cho bạn biết những điều thú vị mà họ đã học được ở sự kiện vừa qua. 

C++ Nga: nó đã xảy ra như thế nào

Ảnh từ album hội nghị

về chúng tôi

Hai sinh viên Trường Đại học Nghiên cứu Quốc gia Trường Kinh tế Cao cấp - St. Petersburg đã làm việc cho vị trí này:

  • Liza Vasilenko là sinh viên đại học năm thứ 4 đang theo học Ngôn ngữ lập trình trong chương trình Toán ứng dụng và Khoa học máy tính. Làm quen với ngôn ngữ C++ trong năm đầu tiên ở trường đại học, sau đó tôi đã có được kinh nghiệm làm việc với nó thông qua quá trình thực tập trong ngành. Niềm đam mê với ngôn ngữ lập trình nói chung và lập trình hàm nói riêng đã để lại dấu ấn trong việc lựa chọn các báo cáo tại hội nghị.
  • Danya Smirnov là sinh viên năm thứ nhất của chương trình thạc sĩ “Lập trình và phân tích dữ liệu”. Khi còn đi học, tôi đã viết các bài toán Olympic bằng C++, và rồi không hiểu sao ngôn ngữ này liên tục xuất hiện trong các hoạt động giáo dục và cuối cùng trở thành ngôn ngữ làm việc chính. Tôi quyết định tham gia hội thảo để nâng cao kiến ​​thức và cũng để tìm hiểu những cơ hội mới.

Trong bản tin, lãnh đạo khoa thường xuyên chia sẻ thông tin về các sự kiện giáo dục liên quan đến chuyên ngành của chúng tôi. Vào tháng XNUMX, chúng tôi thấy thông tin về C++ Russia và quyết định đăng ký làm người nghe. Đây là kinh nghiệm đầu tiên của chúng tôi khi tham gia những hội nghị như vậy.

Cấu trúc hội nghị

  • Báo cáo

Trong suốt hai ngày, các chuyên gia đã đọc 30 báo cáo, bao gồm nhiều chủ đề nóng: sử dụng khéo léo các tính năng ngôn ngữ để giải quyết các vấn đề được áp dụng, cập nhật ngôn ngữ sắp tới liên quan đến tiêu chuẩn mới, các thỏa hiệp trong thiết kế C++ và các biện pháp phòng ngừa khi xử lý hậu quả của chúng, các ví dụ về kiến ​​trúc dự án thú vị cũng như một số chi tiết cơ bản về cơ sở hạ tầng ngôn ngữ. Ba buổi biểu diễn diễn ra đồng thời, thường là hai buổi biểu diễn bằng tiếng Nga và một buổi biểu diễn bằng tiếng Anh.

  • Khu thảo luận

Sau bài phát biểu, tất cả những câu hỏi chưa được đặt ra và những cuộc thảo luận còn dang dở sẽ được chuyển đến các khu vực được chỉ định đặc biệt để giao tiếp với các diễn giả, được trang bị bảng đánh dấu. Một cách hay để giảm bớt thời gian nghỉ giữa các bài phát biểu bằng một cuộc trò chuyện thú vị.

  • Những cuộc nói chuyện chớp nhoáng và những cuộc thảo luận thân mật

Nếu bạn muốn đưa ra một báo cáo ngắn, bạn có thể đăng ký buổi Lightning Talk buổi tối trên bảng trắng và có năm phút để nói về bất cứ điều gì về chủ đề hội nghị. Ví dụ: phần giới thiệu nhanh về chất khử trùng cho C++ (đối với một số nó là mới) hoặc câu chuyện về một lỗi trong quá trình tạo sóng hình sin chỉ có thể nghe thấy chứ không thể nhìn thấy.

Một hình thức khác là cuộc thảo luận nhóm “Với Ủy ban Từ trái tim đến trái tim”. Trên sân khấu là một số thành viên của ủy ban tiêu chuẩn hóa, trên máy chiếu là một lò sưởi (chính thức - để tạo ra một bầu không khí chân thành, nhưng lý do “vì MỌI THỨ ĐANG CHÁY” có vẻ buồn cười hơn), các câu hỏi về tiêu chuẩn và tầm nhìn chung của C++ , không có các cuộc thảo luận kỹ thuật sôi nổi và các kỳ nghỉ lễ. Hóa ra ủy ban cũng có những người còn sống, những người có thể không hoàn toàn chắc chắn về điều gì đó hoặc có thể không biết điều gì đó.

Đối với những người hâm mộ holivar, sự kiện thứ ba vẫn tiếp tục diễn ra - phiên BOF “Go vs. C++”. Chúng tôi chọn một người yêu thích Go, một người yêu thích C++, trước khi bắt đầu buổi học, họ cùng nhau chuẩn bị 100500 slide về một chủ đề (chẳng hạn như các vấn đề với các gói trong C++ hoặc thiếu các generic trong Go), sau đó họ thảo luận sôi nổi với nhau và với khán giả và khán giả cố gắng hiểu hai quan điểm cùng một lúc. Nếu một holivar bắt đầu ngoài bối cảnh, người điều hành sẽ can thiệp và hòa giải các bên. Định dạng này gây nghiện: vài giờ sau khi bắt đầu, chỉ một nửa số slide được hoàn thành. Sự kết thúc phải được tăng tốc rất nhiều.

  • khán đài đối tác

Các đối tác của hội nghị có mặt tại hội trường - tại khán đài, họ nói về các dự án hiện tại, đề nghị thực tập và việc làm, tổ chức các câu đố và cuộc thi nhỏ, đồng thời rút thăm trúng thưởng những giải thưởng hấp dẫn. Đồng thời, một số công ty thậm chí còn đề nghị trải qua những giai đoạn phỏng vấn đầu tiên, điều này có thể hữu ích cho những người đến không chỉ để nghe báo cáo.

Chi tiết kỹ thuật của báo cáo

Chúng tôi đã nghe báo cáo cả hai ngày. Đôi khi rất khó để chọn ra một báo cáo trong số các báo cáo song song - chúng tôi thống nhất chia nhau ra và trao đổi những kiến ​​thức thu được trong giờ giải lao. Và thậm chí như vậy, có vẻ như vẫn còn nhiều điều bị bỏ sót. Ở đây chúng tôi muốn nói về nội dung của một số báo cáo mà chúng tôi thấy thú vị nhất

Các ngoại lệ trong C++ qua lăng kính tối ưu hóa trình biên dịch, Roman Rusyaev

C++ Nga: nó đã xảy ra như thế nào
Trượt từ bài thuyết trình

Như tiêu đề gợi ý, Roman đã xem xét cách xử lý các trường hợp ngoại lệ bằng cách sử dụng LLVM làm ví dụ. Đồng thời, đối với những người không sử dụng Clang trong công việc của họ, báo cáo vẫn có thể đưa ra một số ý tưởng về cách mã có thể được tối ưu hóa. Điều này là như vậy bởi vì các nhà phát triển trình biên dịch và các thư viện tiêu chuẩn tương ứng giao tiếp với nhau và nhiều giải pháp thành công có thể trùng khớp.

Vì vậy, để xử lý một ngoại lệ, bạn cần phải làm rất nhiều việc: gọi mã xử lý (nếu có) hoặc giải phóng tài nguyên ở cấp độ hiện tại và quay lên ngăn xếp cao hơn. Tất cả điều này dẫn đến thực tế là trình biên dịch sẽ thêm các hướng dẫn bổ sung cho các cuộc gọi có khả năng đưa ra các ngoại lệ. Vì vậy, nếu ngoại lệ không thực sự được nêu ra thì chương trình vẫn sẽ thực hiện những hành động không cần thiết. Để giảm chi phí bằng cách nào đó, LLVM có một số phương pháp phỏng đoán để xác định các tình huống trong đó không cần thêm mã xử lý ngoại lệ hoặc có thể giảm số lượng lệnh “bổ sung”.

Người nói sẽ xem xét khoảng một chục trong số chúng và chỉ ra cả hai tình huống trong đó chúng giúp tăng tốc độ thực hiện chương trình và những tình huống mà các phương pháp này không thể áp dụng được.

Do đó, Roman Rusyaev dẫn sinh viên đến kết luận rằng mã chứa việc xử lý ngoại lệ không phải lúc nào cũng được thực thi với chi phí bằng XNUMX và đưa ra lời khuyên sau:

  • khi phát triển thư viện, về nguyên tắc nên bỏ qua các ngoại lệ;
  • nếu vẫn cần có ngoại lệ, thì bất cứ khi nào có thể, bạn nên thêm các công cụ sửa đổi noException (và const) ở mọi nơi để trình biên dịch có thể tối ưu hóa nhiều nhất có thể.

Nhìn chung, diễn giả khẳng định quan điểm rằng tốt nhất nên sử dụng các ngoại lệ ở mức tối thiểu hoặc loại bỏ hoàn toàn.

Slide báo cáo có tại link sau: [“Các ngoại lệ của C++ thông qua lăng kính tối ưu hóa trình biên dịch LLVM”]

Máy phát điện, coroutines và những vị ngọt hấp dẫn khác, Adi Shavit

C++ Nga: nó đã xảy ra như thế nào
Trượt từ bài thuyết trình

Một trong nhiều báo cáo tại hội nghị này dành riêng cho những đổi mới trong C++20 rất đáng nhớ không chỉ vì cách trình bày đầy màu sắc mà còn vì nó xác định rõ ràng các vấn đề hiện có với logic xử lý bộ sưu tập (vòng lặp for, lệnh gọi lại).

Adi Shavit nhấn mạnh những điều sau: các phương thức hiện có sẽ đi qua toàn bộ bộ sưu tập và không cung cấp quyền truy cập vào một số trạng thái trung gian nội bộ (hoặc chúng thực hiện trong trường hợp gọi lại, nhưng có một số lượng lớn các tác dụng phụ khó chịu, chẳng hạn như Callback Hell) . Có vẻ như có các trình vòng lặp, nhưng ngay cả với chúng, mọi thứ cũng không suôn sẻ như vậy: không có điểm vào và ra chung (bắt đầu → kết thúc so với rbegin → rend, v.v.), không rõ chúng ta sẽ lặp lại trong bao lâu? Bắt đầu với C++20, những vấn đề này đã được giải quyết!

Tùy chọn đầu tiên: phạm vi. Bằng cách gói các trình vòng lặp, chúng ta có được giao diện chung cho phần đầu và phần cuối của một vòng lặp, đồng thời chúng ta cũng có được khả năng soạn thảo. Tất cả điều này giúp dễ dàng xây dựng các quy trình xử lý dữ liệu chính thức. Nhưng không phải mọi thứ đều suôn sẻ như vậy: một phần logic tính toán nằm bên trong việc triển khai một trình vòng lặp cụ thể, điều này có thể làm phức tạp mã để hiểu và gỡ lỗi.

C++ Nga: nó đã xảy ra như thế nào
Trượt từ bài thuyết trình

Chà, trong trường hợp này, C++20 đã thêm coroutine (các hàm có hành vi tương tự như các trình tạo trong Python): việc thực thi có thể được trì hoãn bằng cách trả về một số giá trị hiện tại trong khi vẫn duy trì trạng thái trung gian. Do đó, chúng tôi không chỉ làm việc với dữ liệu như nó xuất hiện mà còn gói gọn tất cả logic bên trong một coroutine cụ thể.

Nhưng có một điều đáng lo ngại: hiện tại, chúng chỉ được hỗ trợ một phần bởi các trình biên dịch hiện có và cũng không được triển khai gọn gàng như chúng ta mong muốn: ví dụ: việc sử dụng các tham chiếu và các đối tượng tạm thời trong coroutines là chưa đáng. Ngoài ra, có một số hạn chế đối với những gì có thể là coroutine và các hàm constexpr, hàm tạo/hàm hủy và hàm main không được đưa vào danh sách này.

Do đó, coroutine giải quyết được một phần quan trọng của các vấn đề bằng sự đơn giản của logic xử lý dữ liệu, nhưng việc triển khai hiện tại của chúng cần được cải thiện.

Vật liệu:

Thủ thuật C++ từ Yandex.Taxi, Anton Polukhin

Trong các hoạt động nghề nghiệp của mình, đôi khi tôi phải triển khai những thứ hoàn toàn phụ trợ: trình bao bọc giữa giao diện nội bộ và API của một số thư viện, ghi nhật ký hoặc phân tích cú pháp. Trong trường hợp này, thường không cần thực hiện bất kỳ tối ưu hóa bổ sung nào. Nhưng điều gì sẽ xảy ra nếu những thành phần này được sử dụng trong một số dịch vụ phổ biến nhất trên RuNet? Trong tình huống như vậy, bạn sẽ phải xử lý hàng terabyte mỗi giờ nhật ký! Sau đó, mỗi mili giây đều có giá trị và do đó bạn phải sử dụng nhiều thủ thuật khác nhau - Anton Polukhin đã nói về chúng.

Có lẽ ví dụ thú vị nhất là việc triển khai mẫu con trỏ để triển khai (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_; 
};

Trong ví dụ này, trước tiên tôi muốn loại bỏ các tệp tiêu đề của các thư viện bên ngoài - việc này sẽ biên dịch nhanh hơn và bạn có thể tự bảo vệ mình khỏi các xung đột tên có thể xảy ra và các lỗi tương tự khác. 

Được rồi, chúng tôi đã chuyển #include sang tệp .cpp: chúng tôi cần khai báo chuyển tiếp API được bao bọc, cũng như std::unique_ptr. Giờ đây, chúng tôi có phân bổ động và những thứ khó chịu khác như dữ liệu nằm rải rác trên một loạt dữ liệu và mức độ đảm bảo thấp hơn. std::aligned_storage có thể trợ giúp tất cả điều này. 

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_; 
};

Vấn đề duy nhất: bạn cần chỉ định kích thước và căn chỉnh cho mỗi trình bao bọc - hãy tạo mẫu pimpl của chúng tôi với các tham số , sử dụng một số giá trị tùy ý và thêm dấu kiểm vào hàm hủy để đảm bảo mọi thứ đều đúng: 

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

Vì T đã được xác định khi xử lý hàm hủy nên mã này sẽ được phân tích cú pháp chính xác và ở giai đoạn biên dịch, nó sẽ xuất ra các giá trị kích thước và căn chỉnh cần thiết cần được nhập dưới dạng lỗi. Do đó, với chi phí cho một lần chạy biên dịch bổ sung, chúng tôi loại bỏ phân bổ động của các lớp được bao bọc, ẩn API trong tệp .cpp khi triển khai và cũng có được một thiết kế phù hợp hơn cho bộ xử lý vào bộ đệm.

Việc ghi nhật ký và phân tích cú pháp có vẻ kém ấn tượng hơn và do đó sẽ không được đề cập trong bài đánh giá này.

Slide báo cáo có tại link sau: ["Thủ thuật C++ từ Taxi"]

Các kỹ thuật hiện đại để giữ mã của bạn KHÔ, Björn Fahller

Trong buổi nói chuyện này, Björn Fahller chỉ ra một số cách khác nhau để khắc phục lỗi văn phong của việc kiểm tra điều kiện lặp đi lặp lại:

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

Nghe có vẻ quen? Bằng cách sử dụng một số kỹ thuật C++ mạnh mẽ được giới thiệu trong các tiêu chuẩn gần đây, bạn có thể triển khai chức năng tương tự một cách dễ dàng mà không bị phạt về hiệu suất. So sánh:   

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

Để xử lý số lượng kiểm tra không cố định, ngay lập tức bạn cần sử dụng các mẫu biến thể và biểu thức gấp. Giả sử rằng chúng ta muốn kiểm tra tính bằng nhau của một số biến đối với phần tử state_type của enum. Điều đầu tiên bạn nghĩ đến là viết hàm trợ giúp 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) || ...); 
}

Kết quả trung gian này thật đáng thất vọng. Cho đến nay mã không trở nên dễ đọc hơn:

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

Các tham số mẫu không phải kiểu sẽ giúp cải thiện tình hình một chút. Với sự giúp đỡ của họ, chúng tôi sẽ chuyển các phần tử có thể đếm được của enum vào danh sách các tham số mẫu: 

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

Bằng cách sử dụng auto trong tham số mẫu không phải kiểu (C++ 17), cách tiếp cận chỉ khái quát hóa các so sánh không chỉ với các phần tử state_type mà còn với các kiểu nguyên thủy có thể được sử dụng làm tham số mẫu không phải kiểu:


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

Thông qua những cải tiến liên tiếp này, cú pháp kiểm tra trôi chảy như mong muốn đã đạt được:


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

Trong ví dụ này, hướng dẫn suy luận dùng để đề xuất các tham số mẫu cấu trúc mong muốn cho trình biên dịch, trình biên dịch biết các loại đối số của hàm tạo. 

Hơn nữa - thú vị hơn. Bjorn dạy cách khái quát hóa mã kết quả cho các toán tử so sánh ngoài == và sau đó cho các phép toán tùy ý. Đồng thời, các tính năng như thuộc tính no_unique_address (C++20) và tham số mẫu trong hàm lambda (C++20) được giải thích bằng các ví dụ sử dụng. (Đúng, bây giờ cú pháp lambda thậm chí còn dễ nhớ hơn - đây là bốn cặp dấu ngoặc đơn liên tiếp thuộc đủ loại.) Giải pháp cuối cùng sử dụng các hàm làm chi tiết hàm tạo thực sự khiến tâm hồn tôi ấm áp, chưa kể đến biểu thức tuple trong truyền thống tốt nhất của lambda phép tính.

Cuối cùng, đừng quên đánh bóng nó:

  • Hãy nhớ rằng lambdas là constexpr miễn phí; 
  • Hãy thêm chuyển tiếp hoàn hảo và xem cú pháp xấu xí của nó liên quan đến gói tham số trong bao đóng lambda;
  • Hãy cung cấp cho trình biên dịch nhiều cơ hội hơn để tối ưu hóa với điều kiện không ngoại trừ; 
  • Hãy quan tâm đến lỗi đầu ra dễ hiểu hơn trong các mẫu nhờ các giá trị trả về rõ ràng của lambdas. Điều này sẽ buộc trình biên dịch thực hiện nhiều kiểm tra hơn trước khi hàm mẫu thực sự được gọi - ở giai đoạn kiểm tra kiểu. 

Để biết chi tiết, vui lòng tham khảo tài liệu bài giảng: 

Ấn tượng của chúng tôi

Lần tham gia đầu tiên của chúng tôi vào C++ Russia thật đáng nhớ vì cường độ của nó. Tôi có ấn tượng về C++ Russia như một sự kiện chân thành, nơi ranh giới giữa đào tạo và giao tiếp trực tiếp gần như không thể nhận ra. Mọi thứ, từ tâm trạng của diễn giả đến sự cạnh tranh của các đối tác sự kiện, đều có lợi cho những cuộc thảo luận sôi nổi. Nội dung của hội nghị bao gồm các báo cáo, bao gồm khá nhiều chủ đề bao gồm những đổi mới của C++, nghiên cứu điển hình về các dự án lớn và những cân nhắc về tư tưởng kiến ​​trúc. Nhưng sẽ là không công bằng nếu bỏ qua thành phần xã hội của sự kiện, nó giúp vượt qua rào cản ngôn ngữ không chỉ liên quan đến C++.

Chúng tôi cảm ơn ban tổ chức hội nghị vì đã có cơ hội tham gia vào một sự kiện như vậy!
Có thể bạn đã xem bài viết của ban tổ chức về quá khứ, hiện tại và tương lai của C++ Russia trên blog JUG Ru.

Cảm ơn bạn đã đọc và chúng tôi hy vọng việc kể lại các sự kiện của chúng tôi sẽ hữu ích!

Nguồn: www.habr.com

Thêm một lời nhận xét