百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

解锁 C++ 新姿势:走进 21 世纪的 C++ 编程世界

liebian365 2025-02-13 13:01 7 浏览 0 评论

C++,作为编程领域的老牌语言,自诞生至今已有 45 年之久。在漫长的岁月里,它不断进化,以应对各种新挑战。然而,不少开发者仍用旧眼光看待它,仿佛还停留在上个世纪。但其实,当代 C++ 在表达思想、性能、可靠性和可维护性等方面,都有了质的飞跃。今天,咱们就一起深入了解一下 21 世纪的 C++,看看它到底藏着哪些厉害的机制和特性。

一、现代 C++ 的魅力初体验

先来看一个简单的 C++ 程序,它能从输入中读取每一行内容,并只输出其中的唯一行:

import std;
using namespace std;

int main()
{
    unordered_map m;
    for (string line; getline(cin, line); )
        if (m[line]++ == 0)
            cout << line << '\n';
}

是不是发现,和以前的 C++ 风格相比,这个程序没有了繁琐的内存分配 / 释放操作、尺寸定义、错误处理、类型转换、指针使用、不安全的下标操作,甚至连预处理指令#include都不见了。但它却高效地实现了功能,比很多程序员花时间手写的代码还要快。要是还想提升性能,也能进一步优化。这就是现代 C++ 的魅力,用简洁的代码实现强大的功能。

再看一个变体程序,它收集唯一行供后续使用:

import std;
vector collect_lines(istream& is)
{
    unordered_set s{from_range, istream_iterator{is}};
    return vector{from_range, s};
}

auto lines = collect_lines(cin);

这里用了unordered_set来收集唯一行,最后返回一个vector。而且,编译器能自动推导vector的元素类型,是不是很方便?不过,这里的string还是存在复制操作,后面我们会讲讲怎么优化。这些小例子就是想让大家感受下现代 C++ 的风格,打破对它的固有认知。

二、C++ 的理想追求

C++ 一直以来都有着明确的目标,这些目标贯穿其发展历程:

  1. 直接表达思想:用代码清晰准确地表达开发者的意图。
  2. 静态类型安全:通过静态类型检查,在编译阶段发现类型相关的错误,提高代码的稳定性。
  3. 资源安全(无泄漏):确保程序中使用的资源(如内存、文件句柄等)都能正确释放,避免资源泄漏。
  4. 直接访问硬件:让开发者能够直接操作硬件资源,满足对性能要求极高的场景。
  5. 高性能(高效):生成的代码运行效率高,能充分利用系统资源。
  6. 可扩展(零开销抽象):在不增加额外运行时开销的前提下,方便地进行代码扩展和复用。
  7. 可维护(易理解代码):代码结构清晰,易于阅读和维护。
  8. 平台独立(可移植):编写的代码能在不同的操作系统和硬件平台上运行。
  9. 稳定(兼容):保证与早期版本的 C++ 兼容,让旧代码能在新环境中继续运行。

为了实现这些目标,C++ 不断发展,既有像类的构造函数和析构函数、异常处理、模板、std::vector这样的经典特性,也有模块、概念、Lambda 表达式、范围、constexpr和consteval、并发支持和并行算法、协程、std::shared_ptr等新特性。关键在于,要根据实际问题,合理组合使用这些特性和库功能。

三、资源管理:C++ 的关键能力

在 C++ 里,资源管理至关重要。资源可以是内存、锁、文件句柄等,为了避免资源泄漏,不能依赖手动释放资源。C++ 的基本做法是把资源绑定到一个句柄上,当句柄的作用域结束时,自动释放资源,这就是 RAII(Resource Acquisition Is Initialization)机制。

比如自定义的Vector类:

template
class Vector {
public:
    Vector(initializer_list);
    ~Vector();
private:
    T* elem;
    int sz;
};

这里的Vector就是一个资源句柄,它的构造函数负责分配内存和初始化元素,析构函数负责释放内存和销毁元素。使用时就像这样:

void fct()
{
    Vector constants {1, 1.618, 3.14, 2.99e8};
    Vector designers {"Strachey", "Richards", "Ritchie"};
    Vector> vp { {"producer",prod}, {"consumer",cons}};
}

constants、designers和vp在创建时由构造函数初始化,作用域结束时由析构函数自动释放,而且这种初始化和释放是递归的,即使内部包含复杂的对象,也能妥善处理。

(一)控制对象生命周期

管理资源对象的生命周期对资源管理很关键。C++ 通过构造函数、析构函数、复制构造函数、移动构造函数等操作来控制对象生命周期:

  • 构造函数:在对象首次使用前调用,用于建立类的不变量。
  • 析构函数:在对象最后一次使用后调用,用于释放资源。
  • 复制构造函数和赋值运算符:用于创建和赋值具有相同值的新对象。
  • 移动构造函数和移动赋值运算符:用于在对象间移动资源,通常在不同作用域之间。

(二)消除冗余复制

回到collect_lines函数的例子,之前的版本存在string的复制操作,会影响性能。优化后可以这样写:

vector collect_lines(istream& is)
{
    unordered_set s{from_range, istream_iterator{is}};
    return vector{from_range, std::move(s)};
}

这里通过std::move将set中的元素移动到vector中,避免了不必要的复制。在很多情况下,编译器还会进行 “复制省略” 优化,进一步提高效率。

(三)资源与错误处理

C++ 追求资源安全,在错误情况下也不能泄漏资源。基本规则是:不泄漏资源,不使资源处于无效状态。当检测到无法在本地处理的错误时,要将访问的对象置于有效状态,释放函数负责的资源,让调用链上的其他函数处理资源相关问题。所以,“裸指针” 不能可靠地用作资源句柄。

在处理错误时,C++ 有两种方式:对于常见且能在本地处理的错误,使用错误代码和测试;对于罕见且无法在本地处理的错误,使用异常。基于异常的错误处理不能和用作资源句柄的指针一起使用,要结合异常、RAII 和错误代码来实现可靠的错误处理。

四、模块化:告别旧方式,迎接新变革

C++ 从 C 继承来的预处理器虽然被广泛使用,但它给工具开发和编译器性能带来了不少问题。比如用头文件来实现模块化就存在不少弊端,#include指令会导致包含顺序不同可能有不同含义,而且具有传递性,会造成重复编译和微妙的依赖错误。

而 C++ 现在提供的模块功能就解决了这些问题。模块的导入是顺序无关的,相互独立,能有效避免依赖错误。模块只需要编译一次,大大提高了编译速度。比如一个包含大量代码的库,使用#include编译需要 1.5 秒,而使用模块导入只需要 0.062 秒,速度提升明显。就连标准库也都被做成了模块,像经典的 “hello world” 程序,使用import std;替换#include 后,编译时间从 0.87 秒骤降至 0.08 秒。

五、泛型编程:C++ 的强大武器

泛型编程是现代 C++ 的核心基础之一,它让代码能适用于多种类型,实现代码的简洁、高效和类型安全。C++ 通过模板来支持泛型编程,标准库中到处都有模板的身影,比如容器、算法、并发支持、内存管理、I/O、字符串和正则表达式等。

例如,我们可以写一个排序函数,能对所有符合标准的可排序范围进行排序:

void sort(Sortable_range auto& r);

vector vs;
// … fill vs …
sort(vs);

array ai;
// … fill ai …
sort(ai);

编译器会检查参数类型是否符合Sortable_range的要求,如果不符合,在使用时就会报错。比如对list进行排序就会出错,因为list不支持随机访问。

(一)概念:让泛型编程更强大

概念是 C++20 引入的编译时谓词,用于表达模板参数的要求。比如Sortable_range概念:

template
concept Sortable_range =
    random_access_range
    && sortable>;

它表示一个类型R要是Sortable_range,需要既是random_access_range,迭代器类型又要是sortable。概念还可以用 “使用模式” 直接指定类型的属性,而且我们可以用static_assert显式检查类型是否符合概念。

(二)编译时求值

在现代 C++ 中,很多简单函数都能在编译时求值,像constexpr、consteval和concept。例如:

constexpr auto jul = weekday(December/24/2024); 

为了能在编译时求值,这些函数不能有副作用、访问非局部数据或存在未定义行为,但它们可以使用标准库的很多功能。编译时求值不仅提高了性能,还为代码带来了更多的可能性。

六、遵循准则,保障代码质量

虽然现代 C++ 有诸多优势,但升级代码并不容易,旧习惯也很难改,网上和教材中还有很多过时的信息。为了帮助开发者更好地使用现代 C++,避免陷入语言的 “死角”,人们制定了各种准则,其中 C++ 核心准则是比较有代表性的。

(一)准则的策略

C++ 核心准则采用 “子集 - 超集” 策略:先通过一些库抽象扩展语言,方便高效地使用准则;然后禁止使用低级、低效和易错的特性。这样得到的是更强大、安全、灵活和快速的 C++,同时还是 100% 符合 ISO 标准的 C++,在需要时依然可以使用那些底层特性。

(二)准则示例:不使用指针下标

指针没有关联的范围信息,使用指针下标容易引发内存安全和类型安全问题。比如:

void f(int* p, int n)
{
    for (int i = 0; i < n; i++)
        do_something_with(p[n]);
}
int a[100];
// …
f(a, 100); 
f(a, 1000); 

这里如果n的值不合理,就会访问越界。而使用span可以解决这个问题:

void f(span a) 
{
    for (int& x: s) 
        do_something_with(x);
}
int a[100];
// …
f(a); 
f({a, 1000}); 

span持有指针和元素个数,能进行范围检查,使用起来更安全、简洁。

(三)准则示例:不使用无效指针

有些容器(如vector)在操作时可能会重新分配元素位置,如果外部获取的指向容器元素的指针在重新分配后还被使用,就会出问题。通过遵循 C++ 的使用规则,利用本地静态分析可以防止指针无效化。

(四)强制执行:配置文件

准则虽然有用,但在大型代码库中很难完全遵循,所以强制执行很重要。我们把强制执行的一组连贯的准则规则称为 “配置文件”。目前计划的初始配置文件包括类型、生命周期、边界、算术等方面的检查,未来还会有针对特定应用领域的配置文件。配置文件主要在编译时进行检查,部分重要检查在运行时进行,而且可以根据需要在代码中显式请求或抑制某个配置文件的检查。

七、C++ 的未来展望

C++ 的发展由 ISO 标准委员会控制,目前有很多正在进行的工作,如异步计算模型、静态反射、SIMD、契约系统、函数式编程风格的模式匹配、通用单位系统等,虽然将这些不同的想法整合到一起是个挑战,但也为 C++ 的未来发展带来了无限可能。

八、总结

C++ 在不断发展进化,当代 C++ 在很多方面都比早期版本更接近理想状态,为开发者提供了更好的代码质量、类型安全、表达能力和性能,应用领域也更加广泛。但发展过程中也存在一些问题,比如人们对 C++ 的误解,以及工具支持的滞后。不过,C++ 的模型依然强大,包括静态类型系统、对内置类型和用户定义类型的平等支持、值和引用语义、系统且通用的资源管理、高效的面向对象编程、灵活高效的泛型编程、编译时编程、直接使用机器和操作系统资源、通过库支持并发等。希望大家能深入了解现代 C++,充分利用它的优势,编写出更优秀的代码。你对 C++ 的这些新特性有什么看法呢?欢迎在评论区留言分享!

科技脉搏,每日跳动。

与敖行客 Allthinker一起,创造属于开发者的多彩世界。

- 智慧链接 思想协作 -

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: