现代 C++ 并不可怕
语法不那么可怕
模板参数推断
文字
更多独立于平台的库
在模板类中创建对象
更好的指针
结论
C++ 以晦涩、过时而闻名,只适合那些老古董和愤怒的教授。当然,它很快,但为什么要为了一点点速度而学习所有那些讨厌的开销呢?而且我超爱我的包管理器和工具!C++ 有什么现代的东西吗?
自从大学毕业以来,每当我接触 C++ 或参与 C++ 项目时,我都会有这种感觉。然而,最近我不得不写大量的 C++ 代码,我不得不说……
C++17 不是你爸爸的 C++。
语法不那么可怕
C++ 的语法一向臭名昭著。然而,从 C++11 开始,它的语法逐渐变得易于理解。其中很多都涉及到关键字 的使用。现在你可以像在 Python 和 C# 中一样auto
使用基于范围的循环(技术上是这样):for
foreach
double arr[3] = { 1.0, 2.0, 3.0 };
for (auto& x : arr) {
std::cout << x << std::endl;
}
&
后面的符号auto
表示我们不想复制数组(或std::vector
任何其他可迭代集合)中的项。Auto
可以用来解构std::tuple
s 和std::pair
s,以及struct
s 和数组!如果我们使用上面的数组,我们可以解构整个数组:
auto [a, b, c] = arr;
// auto [a, b] won't compile because our array is length 3
您也可以for
在 s 的循环中使用它:std::map
for(const auto& [k,v] : dictionary) {
...
}
解构元组的工作方式相同:
std::tuple<double, std::string> t{ 4.0, "GPA" };
auto [value, label] = t;
这个元组看起来是用标准的、冗长的 C++ 定义的。好吧,他们也有一个修复方案……
模板参数推断
如果模板类型可以推断类型,就无需指定类型!我可以将std::tuple
上面的定义重写为:
std::tuple t{ 4.0, R"(GPA)" };
// or better yet
auto t = std::tuple{ 4.0, R"(GPA") };
这R"(...)"
会创建一个字符串字面量,而不是字符数组。这比之前的 C++ 语言标准迭代要简洁得多,也让类型签名不再那么烦人。虽然函数返回类型仍然需要完全限定类型,但auto
其他方面可以省去不少麻烦。
语言标准委员会还添加了std::tie
与现有代码集成的功能,您可以将函数的输出值绑定到现有变量上。对于具有多个输出值的函数,新语言确实比以前好用得多,我对此非常满意。
文字
C++ 早已提供了一些用于整数和浮点类型的字面量(例如,将零定义为无符号长整型0UL
,或将浮点型定义为浮点型0.0f
)。在 C++11/14 中,字面量的范围已扩展至布尔值、字符串(UTF-8和UTF-16)、时间单位(42ns
与 相同std::chrono::nanoseconds(42)
)等等!此外,还提供了用户定义字面量的选项,并且其文档非常详尽。对我个人而言,这是最令人兴奋的功能之一!
更多独立于平台的库
C++ 中有些东西是传统意义上操作系统特有的;缺乏总体的抽象。值得庆幸的是,在 C++11 及更高版本中,这个问题已经得到解决。
例如,如果你想让当前线程休眠 5 秒,在祖父的 C++ 中你可以这样写:
#include <unistd.h>
...
void sleep_func(unsigned int milliseconds) {
usleep(milliseconds * 1000);
}
...
unsigned int sleep_seconds = 5;
sleep_func(sleep_seconds * 1000);
这仅适用于 *nix 环境。如果您想以微秒(Unix 实现的度量单位)以外的其他单位执行睡眠,则必须自行进行转换。虽然这并不难,但仍然容易出现人为错误。如果您想在特定线程上执行此操作……那就完全是另一回事了。在 C++11 中,您可以使用std::chrono
带有各种时钟的库,以及用于std::thread
特定线程任务的库。
#include <chrono>
#include <thread>
...
void sleep_func(std::chrono::duration duration) {
std::this_thread::sleep_for(duration);
}
...
auto sleep_seconds = 5;
sleep_func(std::chrono::seconds(sleep_seconds));
命名空间中还预定义了几个持续时间std::chrono
,因此可以非常清楚地了解时间跨度的单位以及编译器如何处理所有转换。这大大减轻了我们的工作量!
他们还最终在 C++17 中实现了文件系统抽象!它在 C++14 中是实验性的,但在最新版本中正式成为语言标准的一部分。
在模板类中创建对象
在 C++ 的“美好旧时光”里,使用模板集合非常烦人。例如,如果你想将某个对象推送到队列的末尾,你必须创建该对象,然后将该对象的副本传递到队列中。
std::vector<thing> things;
for (int i = 0; i < 50; i++) {
thing t("foo", i);
things.emplace_back(t);
}
现在,许多模板类都包含一些之前需要复制的函数,这些函数Args&&
现在都拥有 -style 的实现,因此可以在模板类中创建该类型的新对象!如下所示:
std::vector<thing> things;
for (int i = 0; i < 50; i++) {
things.emplace_back("foo", i);
}
这节省了一些复制开销并加快了速度,并且不鼓励在集合中使用指针(在适当的时候)。
更好的指针
面对现实吧:处理原始指针糟透了。分配内存,释放内存,在一个作用域中创建指针,然后假设所有权稍后会转移到另一个作用域。所有这些情况都需要大量的繁琐手续,可能会导致内存泄漏,并且需要大量的精力来确保不发生内存泄漏。值得庆幸的是,C++11 将 Boost 库中开发的不同指针类型引入了语言规范。
唯一指针
std::unique_ptr<T>
只能被一个对象引用。如果需要将所有权转移到不同的作用域(但仍保留一份副本),可以使用 std::move 来转移所有权。这在工厂中或其他任何需要创建某个对象并将其所有权传递给其他对象的情况下非常有用。例如,您可能希望创建一个来自 的字节流,Socket
并将该数据的所有权传递给请求对象。
共享指针
std::shared_ptr<T>
正式成为我在 C++ 中最喜欢的东西之一。如果某个点需要被多个对象引用(比如,Websocket 的单例),在老式 C++ 中,你会创建一个指向该对象的原始指针,并在清理时或最后一次使用后销毁它……但愿如此。原始指针是 C++ 中内存泄漏的最大元凶之一。它们可能和 NULL 指针一样,都是价值数十亿美元的错误。
值得庆幸的是,共享指针现在已经成为一种流行技术,并在 C++ 社区中被广泛接受。当它们被复制时(它们应该始终被复制,而不是通过引用或指针传递),它们会增加内部引用计数。当这些副本被销毁时,引用计数会减少。当引用计数达到零时,对象将被销毁,内存将被释放。从此告别手动内存管理的麻烦!耶!如果你没有严格复制共享指针,你仍然可能会犯错,但在我看来,现在有一种比共享原始指针更好的安全机制。
结论
虽然感觉 C++ 的语法还是有点多余,但它已经不像我记忆中那么糟糕了。过去 4 个月,我每天都用它来开发,这比我以前的工作要愉快得多。如果你再看看 C++,希望它也不会让你觉得那么可怕!
编码愉快!
鏂囩珷鏉ユ簮锛�https://dev.to/jdsteinhauser/modern-c-isn-t-scary-7ai