免费下载案例集|20+数字化领先企业人才培养实践经验 了解详情
写点什么

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:2019758
用户头像
赵钰莹 InfoQ 主编

发布了 882 篇内容, 共 638.9 次阅读, 收获喜欢 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 · 广东
回复
没有更多了
发现更多内容

让AI发展避开“暗礁”,索信达控股推出自研区块链+联邦学习解决方案

索信达控股

区块链 金融科技 联邦学习 金融监管 风控

数仓备机DN重建:快速修复你的数仓DN单点故障

华为云开发者联盟

数据仓库 主机 华为云 备机 DN

41 位 Contributor 参与,1574 个 PR,不容错过的版本更新!

SphereEx

透过 3.0 Preview 看 Dubbo 的云原生变革

try catch

在北鲲云超算平台上做球体落入水中的流固耦合仿真模拟记录

北鲲云

安卓工控主板显示接口有哪些呢?

双赞工控

安卓主板 工控主板 ARM开发主板

5W1H聊开源之Who和How——谁、如何参与开源?

禅道项目管理

开源 开源文化

Windows 11 这项亮点功能源自英特尔Bridge技术支持

E科讯

CloudQuery 安全系列(一): Http 与 Https

BinTools图尔兹

数据库 网络安全 https 数据安全 数据库管理

数字化时代,为什么解决信任问题是科技公司最重要的事情?

CECBC

奇亚矿机系统源码,Bzz节点分币系统搭建

Vue Conf关于Vite的分享给我带来的启发

前端森林

vite esm Vue 3

解密开源技术的污点

WorkPlus

多路三线RTD电阻温度采集电路设计方案

不脱发的程序猿

电路设计 硬件开发 RTD电阻 温度采集电路

iOS面试残篇-辟邪剑谱

程序员 面试 编程之路 移动开发 iOS 知识体系

性能利器Takin来了!首个生产环境全链路压测平台正式开源

TakinTalks稳定性社区

开源 高可用 性能测试 压力测试

网络攻防学习笔记 Day55

穿过生命散发芬芳

网络攻防 6月日更

技术解密 |阿里云多媒体 AI 团队拿下 CVPR2021 5 冠 1 亚成绩的技术分享

阿里云视频云

阿里云 AI 计算机视觉 经验分享 CVPR

CloudQuery 使用教程之《No.2数据查询(上)》

BinTools图尔兹

sql dba 数据库管理工具 国产数据库 开发运维

深度学习分类任务常用评估指标

华为云开发者联盟

机器学习 深度学习

存储大师班:NFS 的诞生与成长

青云技术社区

存储 分布式存储 NFS

深入C语言中数据的存储

小写丶H

数字人民币双层运营架构下缘何衍生出2.5层?看完才明白,原来这么重要!

CECBC

Java 的函数式接口(必懂知识点!)

Java MySQL 程序员 面试

社群编码识别黑灰产攻击实践

百度Geek说

我的新书《C++服务器开发精髓》终于出版啦

张小方

c++ 网络编程 Linux服务器开发 C++后端开发 网路通信

有没有字节工牌,Java并发安全的根本原因都得懂

慕枫技术笔记

Java 高并发

未来法律科技发展现五大趋势,区块链、AI、大数据吸引资本目光

CECBC

dubbo 2.7应用级服务发现踩坑小记

捉虫大师

dubbo 服务发现

阿里最新秋招面经,腾讯/美团/字节1千道Java中高级面试题

Java 编程 程序员 架构 面试

字节跳动三面拿offer:网络+IO+redis+JVM+GC+红黑树+数据结构

Java 编程 程序员 架构 面试

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