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++ 设计中的妥协以及处理其后果时的预防措施、示例有趣的项目架构,以及语言基础设施的一些底层细节。 三场演出同时进行,通常两场用俄语,一场用英语。

  • 讨论区

演讲结束后,所有未提出的问题和未完成的讨论都被转移到专门指定的区域与演讲者交流,并配备了标记板。 这是通过愉快的谈话来消磨演讲间隙的好方法。

  • 闪电演讲和非正式讨论

如果您想做一个简短的报告,您可以在白板上报名参加晚上的闪电演讲,并获得五分钟的时间来谈论有关会议主题的任何内容。 例如,对 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 博客上.

感谢您的阅读,希望我们对事件的重述对您有所帮助!

来源: habr.com

添加评论