C++

C++20:标准库

2020-02-24  本文已影响0人  奇点创客

原文详见:C++20: The Library

在上篇文章 C++20:核心语言 中我们介绍了 C++20 的核心语言部分。今天,我们将继续来了解 C++20 标准库部分。

上图展示了我今天的计划。

标准库

日历(Calendar)和时区(Time-Zone)

新标准在 C++11/14 的 chrono 库的基础上拓展出了日历和时区设施。如果你还不了解 chrono 库,请移步至我关于时间的系列文章。

日历(Calendar)

一个日历可以由年、月、日以及周这些类型组成。这些基础类型之间可以组合从而形成复杂类型,例如:年月、年月日、年月日剩余、年月周、年月周剩余等。标准库还对操作符 “/” 进行了重载以方便指定时间点。另外,在 C++20 中我们还将获得两个新的字面量:用 d 表示日,用 y 表示年。

时区(Time-Zone)

时间点的格式可以依据各种具体时区来进行展示。
由于拓展了 chrono 库,可以很简单地来实现以下用例:

auto d1 = 2019y/oct/28;
auto d2 = 28d/oct/2019;
auto d3 = oct/28/2019; 
#include "date.h"
#include <iostream>

int main()
{
    using namespace date;
    using namespace std::chrono;
    auto now = system_clock::now();
    std::cout << "The current time is " << now << " UTC\n";
    auto current_year = year_month_day{floor<days>(now)}.year();
    std::cout << "The current year is " << current_year << '\n';
    auto h = floor<hours>(now) - sys_days{jan/1/current_year};
    std::cout << "It has been " << h << " since New Years!\n";
}
当然,C++20 中使用 std::chrono 命名空间代替了 date 命名空间。以下是该程序的输出:
std::span

std::span 可以看做是对一个连续序列对象的引用,但它从不拥有对象本身,因此有时也称它为视图。这种连续序列可以是一个数组、一个拥有大小的指针或者一个 std::vector。一种典型的实现通常由一个指向首元素地址的指针和一个表示序列大小的量组成。std::span<T> 存在的主要原因是一个普通数组在传递给函数后会退化为一个指针,然而,std::span<T> 却能够自动推断出普通数组或 std::vector 的大小。如果你想使用指针去初始化 std::span<T>,那请务必再给它提供大小。

template <typename T>
void copy_n(const T* p, T* q, int n){}

template <typename T>
void copy(std::span<const T> src, std::span<T> des){}

int main()
{    
  int arr1[] = {1, 2, 3};
  int arr2[] = {3, 4, 5};
  
  copy_n(arr1, arr2, 3);         // (1)
  copy(arr1, arr2);              // (2)    
}
constexpr 容器

C++ 变得越来越 constexpr 化了。例如,许多 STL 算法在 C++20 中都用 constexpr 进行了重载。对一个函数或函数模板来说,constexpr 意味着其在编译器就可能被执行。那么,那些容器可以在编译器使用呢?C++20 支持 std::string 和 std::vector。

在 C++20 之前,它们都不能使用 constexpr,因为存在以下三方面的限制:

  1. 析构函数不能是 constexpr
  2. 无法进行动态内存的申请和释放
  3. 无法使用 placement-new 来就地构造

这三个限制现已解决。
第三点谈到的 placement-new 或许鲜为人知。所谓 placement new 就是在已分配的内存位置实例化新的对象。此外,你还可以为全局或你的数据类型来重载 placement-new。

char* memory = new char[sizeof(Account)];        // 申请内存
Account* account = new(memory) Account;          // 就地构造
account->~Account();                             // 析构
delete [] memory;                                // 释放内存

以上就是使用 placement-new 的步骤。第一行为 Account 分配内存,第二行就地构造 account。诚然,第三行的 account->~ account() 表达式看起来很奇怪,这种表达式是一种罕见的情况,在这种情况下必须显式地调用析构函数。最后一行释放内存。

我就不再深入到容器的构造细节了。如果你还有兴趣,建议阅读 784R1

std::format

对于全新的 format 库,cppreference.com 上有一个简洁的描述:“文本格式化库(std::format)提供了 printf 函数族的安全且可扩展的替代品。有意使之补充现存的 C++ I/O 流库并复用其基础设施,例如对用户定义类型重载的流插入运算符”。下例是对该描述直截了当的展现:

std::string message = std::format("The answer is {}.", 42);

也许,这让你想起了 Python 风格的格式化字符串,正是这样。 GitHub 上已经有 std::format 的实现:fmt。以下是上述实现中的一些示例,它使用std命名空间 fmt 代替 std。

std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right."
fmt::memory_buffer buf;
format_to(buf, "{}", 42);    // replaces itoa(42, buffer, 10)
format_to(buf, "{:x}", 42);  // replaces itoa(42, buffer, 16)
// access the string with to_string(buf) or buf.data()
struct date {
  int year, month, day;
};

template <>
struct fmt::formatter<date> {
  template <typename ParseContext>
  constexpr auto parse(ParseContext &ctx) { return ctx.begin(); }

  template <typename FormatContext>
  auto format(const date &d, FormatContext &ctx) {
    return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
  }
};

std::string s = fmt::format("The date is {}", date{2012, 12, 9});
// s == "The date is 2012-12-9"

接下来?

正如我承诺的那样,我将在以后的文章中深入探讨标准库。但首先,我必须完成对 C++20 的高级概述。下一篇文章我将介绍并发特性。

上一篇 下一篇

猜你喜欢

热点阅读