写点什么

C++ 20 的悲叹,未出世就被群嘲“劝退”

  • 2019-01-02
  • 本文字数:5667 字

    阅读完需:约 19 分钟

C++ 20的悲叹,未出世就被群嘲“劝退”

为了 C++20,C++标准委员会曾举办历史上规模最大的一次会议(180 人参会),试图通过会议确定哪些特性可以加入新版本,我们也已经看到媒体爆料的部分新特性,比如 Concepts、Ranges、Modules、Coroutines 等,但大部分开发人员并不认可此次调整,并将部分新特性归结为“语法糖”。



不少网友看到上述特性纷纷在社交平台吐槽,表示不看好 C++20 版本的发布:


图片描述


不仅国内如此,国外的一位游戏领域开发人员接连在社交平台发表看法,声明自己不看好 C++20 的新特性,并认为新版本没有解决最关键的问题,他通过使用毕达哥拉斯三元数组示例对 C++20 标准下的代码和旧版本进行对比,明确阐述自己对于 C++20 的态度。


毕达哥拉斯三元数组,C ++ 20 Ranges 风格


以下是 C++20 标准下代码的完整示例:


// A sample standard C++20 program that prints// the first N Pythagorean triples.#include <iostream>#include <optional>#include <ranges>   // New header! using namespace std; // maybe_view defines a view over zero or one// objects.template<Semiregular T>struct maybe_view : view_interface<maybe_view<T>> {  maybe_view() = default;  maybe_view(T t) : data_(std::move(t)) {  }  T const *begin() const noexcept {    return data_ ? &*data_ : nullptr;  }  T const *end() const noexcept {    return data_ ? &*data_ + 1 : nullptr;  }private:  optional<T> data_{};}; // "for_each" creates a new view by applying a// transformation to each element in an input// range, and flattening the resulting range of// ranges.// (This uses one syntax for constrained lambdas// in C++20.)inline constexpr auto for_each =  []<Range R,     Iterator I = iterator_t<R>,     IndirectUnaryInvocable<I> Fun>(R&& r, Fun fun)        requires Range<indirect_result_t<Fun, I>> {      return std::forward<R>(r)        | view::transform(std::move(fun))        | view::join;  }; // "yield_if" takes a bool and a value and// returns a view of zero or one elements.inline constexpr auto yield_if =  []<Semiregular T>(bool b, T x) {    return b ? maybe_view{std::move(x)}             : maybe_view<T>{};  }; int main() {  // Define an infinite range of all the  // Pythagorean triples:  using view::iota;  auto triples =    for_each(iota(1), [](int z) {      return for_each(iota(1, z+1), [](int x) {        return for_each(iota(x, z+1), [](int y) {          return yield_if(x*x + y*y == z*z,            make_tuple(x, y, z));        });      });    });    // Display the first 10 triples    for(auto triple : triples | view::take(10)) {      cout << '('           << get<0>(triple) << ','           << get<1>(triple) << ','           << get<2>(triple) << ')' << '\n';  }}
复制代码


以下代码为简单的 C 函数打印第一个 N Pythagorean Triples:


void printNTriples(int n){    int i = 0;    for (int z = 1; ; ++z)        for (int x = 1; x <= z; ++x)            for (int y = x; y <= z; ++y)                if (x*x + y*y == z*z) {                    printf("%d, %d, %d\n", x, y, z);                    if (++i == n)                        return;                }}
复制代码


如果不必修改或重用此代码,那么一切都没问题。 但是,如果不想打印而是将三元数组绘制成三角形或者想在其中一个数字达到 100 时立即停止整个算法,应该怎么办呢?


毕达哥拉斯三元数组,简单的 C ++风格


以下是旧版本的 C++代码实现打印前 100 个三元数组的完整程序:


// simplest.cpp#include <time.h>#include <stdio.h>int main(){    clock_t t0 = clock();
int i = 0; for (int z = 1; ; ++z) for (int x = 1; x <= z; ++x) for (int y = x; y <= z; ++y) if (x*x + y*y == z*z) { printf("(%i,%i,%i)\n", x, y, z); if (++i == 100) goto done; } done:
clock_t t1 = clock(); printf("%ims\n", (int)(t1-t0)*1000/CLOCKS_PER_SEC); return 0;}
复制代码


我们可以编译这段代码:clang simplest.cpp -o outsimplest,需要花费 0.064 秒,产生 8480 字节可执行文件,在 2 毫秒内运行并打印数字(使用的电脑是 2018 MacBookPro,Core i9 2.9GHz,Xcode 10 clang):


(3,4,5)(6,8,10)(5,12,13)(9,12,15)(8,15,17)(12,16,20)(7,24,25)(15,20,25)(10,24,26)...(65,156,169)(119,120,169)(26,168,170)
复制代码


这是 Debug 版本的构建,优化的 Release 版本构建:clang simplest.cpp -o outsimplest -O2,编译花费 0.071 秒,生成相同大小(8480b)的可执行文件,并在 0ms 内运行(在 clock()的计时器精度下)。


接下来,对上述代码进行改进,加入代码调用并返回下一个三元数组,代码如下:


// simple-reusable.cpp#include <time.h>#include <stdio.h>struct pytriples{    pytriples() : x(1), y(1), z(1) {}    void next()    {        do        {            if (y <= z)                ++y;            else            {                if (x <= z)                    ++x;                else                {                    x = 1;                    ++z;                }                y = x;            }        } while (x*x + y*y != z*z);    }    int x, y, z;};int main(){    clock_t t0 = clock();
pytriples py; for (int c = 0; c < 100; ++c) { py.next(); printf("(%i,%i,%i)\n", py.x, py.y, py.z); }
clock_t t1 = clock(); printf("%ims\n", (int)(t1-t0)*1000/CLOCKS_PER_SEC); return 0;}

复制代码


这几乎在同一时间编译和运行完成,Debug 版本文件变大 168 字节,Release 版本文件大小相同。此示例编写了 pytriples 结构,每次调用 next()都会跳到下一个有效三元组,调用者可随意做任何事情,此处只调用一百次,每次打印三联。


虽然实现的功能等同于三重嵌套 for 循环,但 C++ 20 标准下的代码让人感觉不是很清楚,无法立即读懂程序逻辑。如果 C ++有类似 coroutine 的概念,就可能实现三元组生成器,并且和原始的 for 循环嵌套一样清晰:


generator<std::tuple<int,int,int>> pytriples(){    for (int z = 1; ; ++z)        for (int x = 1; x <= z; ++x)            for (int y = x; y <= z; ++y)                if (x*x + y*y == z*z)                    co_yield std::make_tuple(x, y, z);}
复制代码


C ++20 Ranges 会让整段代码更加清晰吗?结果如下:


auto triples =    for_each(iota(1), [](int z) {        return for_each(iota(1, z+1), [](int x) {            return for_each(iota(x, z+1), [](int y) {                return yield_if(x*x + y*y == z*z,                    make_tuple(x, y, z));                });            });        });
复制代码


多次 return 实在是让人感觉很奇怪,这或许不应该成为好语法的标准。

C++存在的问题有哪些?

如果谈到 C++的问题,至少有两个:一是编译时间;二是运行时性能。虽然 C++ 20 Ranges 还未正式发布,但本文使用了它的近似版,即 isrange-v3(由 Eric Niebler 编写),并编译了规范的“Pythagorean Triples with C ++ Ranges”示例:


// ranges.cpp#include <time.h>#include <stdio.h>#include <range/v3/all.hpp>using namespace ranges;int main(){    clock_t t0 = clock();
auto triples = view::for_each(view::ints(1), [](int z) { return view::for_each(view::ints(1, z + 1), [](int x) { return view::for_each(view::ints(x, z + 1), [](int y) { return yield_if(x * x + y * y == z * z, std::make_tuple(x, y, z)); }); }); });
RANGES_FOR(auto triple, triples | view::take(100)) { printf("(%i,%i,%i)\n", std::get<0>(triple), std::get<1>(triple), std::get<2>(triple)); }
clock_t t1 = clock(); printf("%ims\n", (int)(t1-t0)*1000/CLOCKS_PER_SEC); return 0;}
复制代码


该代码使用 0.4.0 之后的版本,并用 clang ranges.cpp -I. -std=c++17 -lc++ -o outranges 编译,整个过程花费 2.92 秒,可执行文件为 219 千字节,运行在 300 毫秒之内。


这是一个非优化的构建,优化构建版本(clang ranges.cpp -I. -std=c++17 -lc++ -o outranges -O2)在 3.02 秒内编译,可执行文件为 13976 字节,并在 1ms 内运行。因此运行时性能很好,可执行文件稍大,编译时问题仍然存在。


C++20 比简单版本的代码编译时间长近 3 秒


编译时是 C ++的一个大问题,这个非常小的例子编译时间比简单版的 C ++长 2.85 秒。在 3 秒内,现代 CPU 可以进行大量操作,比如,在 Debug 构建中编译一个包含 22 万行代码的数据库引擎(SQLite)只需要 0.9 秒。所以,编译一个简单的 5 行示例代码比运行完整的数据库引擎慢三倍?


在开发过程中,C ++编译时间一直是大小代码库的痛苦根源。他认为,C ++新版本应该把解决编译时问题排在第一位。但是,整个 C++社区好像并不知道该问题,每个版本都将更多内容放入头文件,甚至放入必须存在于头文件的模板化代码中。


range-v3 是 1.8 兆字节的源代码,全部在头文件中,因此,虽然使用 C++ 20 输出 100 个三元数组的代码示例只有 30 行,但加上头文件后,编译器最终会编译 102,000 行代码。在所有预处理之后,简单版本的 C ++示例只有 720 行代码。


调试构建性能差


Ranges 示例的运行时性能慢了 150 倍,这对于要解决实际问题的代码库而言,两个数量级的速度可能意味着不会对任何实际数据集起作用。该开发者在游戏行业工作,这意味着引擎或工具的 Debug 版本不适用于任何真实的游戏级别模拟(性能无法接近所需的交互级别)。


通过避免 STL 位(提交),可以让最终运行时快 10 倍,也可以让编译时间更快且调试更容易,因为微软的 STL 实现特别喜欢深度嵌套的函数调用。这并不是说 STL 必然不好,有可能编写 STL 实现在非优化版本中不会变慢 10 倍(如 EASTL 或 libc ++那样),但由于微软的 STL 过度依赖深度嵌套,因此会变慢。


作为语言的使用者,大部分人不关心它是否正确发展!即便知道 STL 在 Debug 中太慢,宁愿花时间修复或者研究替代方案(例如不使用 STL,重新实现需要的位,或者完全停止使用 C ++)也不会花时间整理论文上报 C++委员会,这太浪费时间。

其他语言如何?

这里简要介绍 C#中“毕达哥拉斯三元数组”实现,以下是完整 C#源代码:


using System;using System.Diagnostics;using System.Linq;class Program{    public static void Main()    {        var timer = Stopwatch.StartNew();        var triples =            from z in Enumerable.Range(1, int.MaxValue)            from x in Enumerable.Range(1, z)            from y in Enumerable.Range(x, z)            where x*x+y*y==z*z            select (x:x, y:y, z:z);        foreach (var t in triples.Take(100))        {            Console.WriteLine($"({t.x},{t.y},{t.z})");        }        timer.Stop();        Console.WriteLine($"{timer.ElapsedMilliseconds}ms");    }}
复制代码


就个人而言,C#可读性较高:


var triples =    from z in Enumerable.Range(1, int.MaxValue)    from x in Enumerable.Range(1, z)    from y in Enumerable.Range(x, z)    where x*x+y*y==z*z    select (x:x, y:y, z:z);
复制代码


用 C ++:


auto triples = view::for_each(view::ints(1), [](int z) {    return view::for_each(view::ints(1, z + 1), [](int x) {        return view::for_each(view::ints(x, z + 1), [](int y) {            return yield_if(x * x + y * y == z * z,                std::make_tuple(x, y, z));        });    });});
复制代码


C#LINQ 的另一种“数据库较少”的形式:


var triples = Enumerable.Range(1, int.MaxValue)    .SelectMany(z => Enumerable.Range(1, z), (z, x) => new {z, x})    .SelectMany(t => Enumerable.Range(t.x, t.z), (t, y) => new {t, y})    .Where(t => t.t.x * t.t.x + t.y * t.y == t.t.z * t.t.z)    .Select(t => (x: t.t.x, y: t.y, z: t.t.z));
复制代码


在 Mac 上编译这段代码,需要使用 Mono 编译器(本身是用 C#编写的),版本 5.16。mcs Linq.cs 需要 0.20 秒。相比之下,编译等效的简单 C#版本需要 0.17 秒。LINQ 样式为编译器创建了额外的 0.03 秒。但是,C ++却创造了额外的 3 秒。


一般来说,我们会试图避免大部分 STL,使用自己的容器,哈希表使用开放寻址代替…甚至不需要标准库的大部分功能。但是,难免需要时间说服每一位新员工(尤其是应届生),因为 C++20 被称为现代 C ++,很多新员工认为“新一定就是好”,其实并不是这样。

为什么 C ++会这样?

该开发者表示不太清楚 C++为什么会发展到现在这个地步。 但他个人认为,C++社区需要学会“保持接近 100%向后兼容的同时发展一种语言”。 在某种程度上,现在的 C++生态系统太专注于炫耀或证明其价值的复杂性,却并不易用。


在他的印象中,大多数游戏开发人员还停留在 C++ 11、14 或者 17 版本,C++20 基本忽略了一个问题,无论什么被添加到标准库,编译时间长和调试构建性能差的问题没有解决都是无用的。


对于游戏产业而言,大部分传统技术是用 C 或 C ++构建的,在很长一段时间内,没有出现可行的替代品(好在目前至少可以用 Rust 作为可能的竞争者),对 C 和 C ++的依赖程度很高,需要社区的一些帮助和回应。


参考链接:http://aras-p.info/blog/2018/12/28/Modern-C-Lamentations/


2019-01-02 08:2019859
用户头像
赵钰莹 极客邦科技 总编辑

发布了 882 篇内容, 共 645.6 次阅读, 收获喜欢 2679 次。

关注

评论 9 条评论

发布
用户头像
更正一下:STL is too slow in Debug 指的是编译器debug模式
这个确实是,因为模板代码跟普通代码不一样,模板代码编译器“决定权”占比要大的多,最经典的例子就是inline, stl里很少直接标注inline, 因为inline不inline决定权几乎完全在编译器,普通代码则有一定的决定权。

2021-11-09 20:36
回复
用户头像
最后说一下:
这老哥还说了一些关于c的什么什么,懒得反驳。 2021年在看c++20, 虽然没与11那样具有里程碑的意义,还是可圈可点的,既兼顾了higher-level了,也有lower。同时提出了一种不同与OOP的编程思想,如果真的实现,可能会颠覆整个软件世界。
ps:这网站能不能早点专业的人翻译啊?别翻译些啊猫啊狗的博客啊? Devid, Douglas,
Anthony, Hans 这些人的博客不香吗?
2021-11-09 20:23
回复
用户头像
4:最后图穷匕现。第一个是“the companies have already wrote their own containers/strings/algorithms/… years ago”(意思就是stl没有用,如果需要我们自己会写,你们committees浪费时间,等等), 第二个是“C++ is nuts and is not what we want”
反驳:stl可以说是几乎所有c++ programmer的启蒙老师,它提供了一个generic范式,提供一种普适的解决方案,不可能适合所有应用场景,去要求do everything with stl is enough,可能吗?写了20年的c++,我敢肯定也是师从stl,回过头来骂老师,行!真行!,第二个跟不值一驳了,你们搞游戏引擎的要求就是我们的要求?就应该是committees的要求?太自大了吧?老哥
2021-11-09 20:11
回复
用户头像
3 : 接着就是开始集中喷STL,看了下。两个方面,第一个方面是“compilation Times Are a Big Issue”,第二个方面就是“STL is too slow in Debug”。
反驳:generic是种什么思维啊?是高抽象思维,高抽象必然意味着低效率(这个效率指的是编译效率,而非运行效率),不会吧不会吧写了20年的c++,这还要我说的啊?,generic debug当然难啊,如果知道实例化过程完全有迹可循,吃了generic的红利还不想付出点代价,哪里有这么好的事?
2021-11-09 19:42
回复
用户头像
2: 用simple C++ style 对比 generic style, 得出一个结论:“Maybe that ....right now.”(意思就是说:你看simple style可读性多高,还拿perl反讽一下,同时还自我嘲讽一下);
反驳:我都不想反驳了,送他一句话:pls, get fk back to c.
2021-11-09 19:29
回复
用户头像
粗略的看了下原文章,这网站的翻译是个二把刀,没明白这作者在说啥。作者主要说了一下几点:
1:他说的这个“一是编译时间;二是运行时性能”,主要是指STL,原文“Issues with Everything is a library” 。
反驳:这完全是一种c的思维,往作者背景看了一眼,怪不得。。。搞游戏引擎开发的。。。我作为搞分布式的,我当然有意见。。分布式要求高抽象,高效率。游戏引擎唯一也是最高要求就是效率,一切为效率让步。(这货喷c++跟llinus一个观点)
2021-11-09 19:21
回复
用户头像
真搞笑,C++的例子generic and constexpr, 与c#的例子抽象程度都不一样,c++ constexpr编译期行为当然编译时间久(其目的就是把“已知留编译期,把未知留在运行期”),搞笑的是敢让C#骑在c++头上举例,我还是第一次见!!!!最搞笑的是“业内人事举例什么什么”,哪个业内人士啊,这种代码风格?,把名字列出来好吗?半桶水就隔着指点江山,不害臊!
2021-11-09 18:31
回复
用户头像
真搞笑,C++的例子generic and constexpr, 与c#的例子抽象程度都不一样,c++ constexpr编译期行为当然编译时间久(其目的就是把“已知留编译期,把未知留在运行期”),搞笑的是敢让C#骑在c++头上举例,我还是第一次见!!!!最搞笑的是“业内人事举例什么什么”,哪个业内人士啊,这种代码风格?,把名字列出来好吗?半桶水就隔着指点江山,不害臊!
ps:无意之间看见的这篇文章,特意注册来反驳一下,简直误人子弟,怪不得国内c++不行。
2021-11-09 18:30
回复
特地注册过来点赞评论!国内C++环境太差了,媒体也是不负责任,大家都喜欢偷懒图快图省事!
2023-10-07 09:59 · 广东
回复
没有更多了
发现更多内容

数据库是什么意思?有什么用?有哪些类型?

行云管家

数据库 运维 IT

一文搞懂音视频开发技术点及职业发展方向

赖猫

c++ android 音视频

如何在MacOS上无缝切换Win11和MacOS?

Zhendong

MacBook m1 Parallels

架构训练营 模块一

Leach Sun

springboot项目集成docker

try catch

Docker Dockerfile springboot

百度短视频推荐系统的目标设计

百度Geek说

架构 后端 推荐系统 短视频

太强了!京东首席架构师深邃洞察:服务化+云原生+微服务

Java~~~

Java 架构 面试 云原生 架构师

浅析 DDD 领域驱动设计

牧小农

DDD 领域驱动

把工作讲给家人听

FunTester

读书笔记 FunTester 奈非文化手册 办公效率 居家工作

阿里内部最新“SpringCloudAlibaba学习笔记”出炉

Java 阿里巴巴 架构 面试 微服务

「技术点串烧」☕【Java 技术指南】「难点-核心-遗漏」Java线程状态流转及生命周期的技术指南!

洛神灬殇

Java 线程 Thread 9月日更

微信或推出聊天记录云备份付费服务,你的微信记录值多少钱?

郑州埃文科技

云服务 微信聊天 数据风险管理

C/S结构是什么意思?有什么优点?

行云管家

数据库 运维 IT

必杀器!鹅厂首推569页Netty+Redis+ZK+高并发

Java~~~

Java redis 架构 面试 Netty

收藏!阿里P9耗时28天,总结历年亿级活动高并发系统设计手册

Java~~~

Java 架构 面试 高并发 系统

无敌!阿里巴巴开源落地可实操项目:网约车+咚宝商城+英雄传说

Java~~~

Java 架构 面试 项目 架构师

你的 SQL 还在回表查询吗?快给它安排上覆盖索引

Java MySQL 数据库 后端

打爆怪兽 一起来养猪 养蜂人 幸福饭店

游戏开发_软件开发

软件 App 开发 游戏 语音合成

精品!阿里P7爆款《K8s+Jenkins》技术笔记,高质量干货必收藏

Java 程序员 架构 面试 k8s

低代码的5个误区,你踩雷了吗?

禅道项目管理

低代码 开发

华为云GaussDB首次亮相2021服贸会,为数字人民币提供坚实数据底座

华为云数据库小助手

金融科技 数字经济 GaussDB 华为云数据库

在同一台计算机中运行多个MySQL服务

Java 数据库 后端 msyql

安卓工控主板双网口有什么用途?

双赞工控

安卓主板 工控主板

为什么UI自动化难做?—— 关于Selenium UI自动化的思考

LigaAI

测试 UI自动化

阿里内部架构解密:网络+分布式+RPC+消息中间件+微服务

Java~~~

Java spring 架构 面试 微服务

别慌!阿里专家破SpringBoot:入门+基础+进阶+项目

Java~~~

Java 数据库 架构 面试 Spring Boot

横空出世!复盘B站面试坑我最深的Java并发:JDK源码剖析

Java~~~

Java 源码 架构 jdk 面试

高性能利器:CDN我建议你好好学一下!

九灵

Java 分布式 微服务 CDN

令我入职阿里的750页微服务架构深度解析文档有何神秘之处?

Java 编程 架构 面试 架构师

狂刷《Java权威面试指南(阿里版)》,冲击“金九银十”有望了

Java 编程 架构 面试 程序人生

“小巨人”的转型烦恼,百度智能云能否解压?

百度开发者中心

人工智能 企业资讯 中小企业

C++ 20的悲叹,未出世就被群嘲“劝退”_编程语言_赵钰莹_InfoQ精选文章