C++23 新特性概览 核心语言部分

显式对象形参(Explicit Object Parameters)

语法:

class Object {
 public:
  int GetId() { return id_; }

 private:
  int id_ = 0;
};

其中 GetId 可以改写为:

class Object {
 public:
  int GetId(this Object& self) { return self.id_; }

 private:
  int id_ = 0;
};

使用场景1: 重载运算符

class Object {
 public:
  Object(std::string name) : name_{std::move(name)} {}

  // 此处有三个重载
  const std::string& GetName() const& { return name_; }
  std::string& GetName() & { return name_; }
  std::string&& GetName() && { return std::move(name_); }

 private:
  std::string name_;
};

可以改写为:

class Worker {
public:
    Worker(std::string name) : name_{ std::move(name) } {}

    template <typename Self>
    auto&& GetName(this Self&& self) {
        return std::forward<Self>(self).name_;
    }

private:
    std::string name_;
};

进一步参考:

if consteval

语法:

if consteval { /* A */ } else { /* B */ }

语义:
如果语句是在常量表达式上下文中执行的,那么执行A,否则执行B。

#include <cassert>

consteval int f(int i) { return i; }

constexpr int g(int i) {
  if consteval {
    // 编译时计算
    return f(i) + 1;
  } else {
    return 42;
  }
}

int main() {
  // 相同的函数调用在不同的上下文中有不同的行为
  static_assert(g(1) == 2);
  assert(g(1) == 42);
}

从上面的例子中可以看到, 相同的函数调用g(1), 在编译时运行会得到2, 在运行时会得到42.

多维数组下标(Multidimensional Subscript Operator)

#include <array>
#include <cassert>
#include <iostream>

template <typename T, std::size_t Z, std::size_t Y, std::size_t X>
struct Array3d {
  std::array<T, X * Y * Z> m{};

  constexpr T& operator[](std::size_t z, std::size_t y, std::size_t x)  // C++23
  {
    assert(x < X and y < Y and z < Z);
    return m[z * Y * X + y * X + x];
  }
};

int main() {
  // Multidimensional Subscript Operator
  Array3d<int, 4, 4, 4> v;
  std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
  v[3, 2, 1] = 42;
  std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
}

lambada表达式的特性

在C++23以前, lambda表达式可以有属性. 如下:

auto l = []() [[deprecated]] { return 42; };

在C++23中, lambda的调用可以有属性:

auto l = [] [[nodiscard]] () [[deprecated]] { return 42; };
[[maybe_unused]] int a = l();

进一步参考:

size_t 字面量后缀

已有的数字字面量后缀: U, L, UL, LL, ULL.

新增的数字字面量后缀:

  • zZ 表示 有符号版本的std::size_t
  • uzUZ 表示 std::size_t
#include <iostream>
#include <vector>

int main() {
  std::vector<int> vec{1, 2, 3, 4, 6};
  for (auto i = 0uz; i < vec.size(); i++) {
    std::cout << vec[i] << ' ';
  }
  static_assert(std::is_same<decltype(10uz), decltype(vec.size())>::value ==
                true);
  static_assert(std::is_same<decltype(10z), ssize_t>::value == true);
}

auto(x): decay copy

当前的auto关键字用于类型推导

auto x = 42;  // x is lvalue

C++23 中新增的auto(x),auto {x}用来创建一个prvalue的副本.

样例:

void process(int&& value) { std::cout << value << std::endl; }
void process_all(const std::vector<int>& data) {
  for (auto& i : data) {
    process(auto(i));
  }
}

#elifdef, #elifndef, #warning

已有的的预处理指令:

  • #ifdef ID#if defined(ID) 的缩写
  • #ifndef ID#if !defined(ID) 的缩写

C++23中新增的预处理指令:

  • #elifdef ID#elif defined(ID) 的缩写
  • #elifndef ID#elif !defined(IDENTIFIER) 的缩写

#warning 指令用于生成编译器警告信息.

#warning "This is a warning message"

标记不可达代码(std::unreachable())

std::unreachable() 常被用于标记不可达代码. 如果代码执行到这里, 就会产生未定义的行为.

何谓"未定义行为"? 未定义行为是指程序在运行时可能产生任何结果, 包括崩溃, 输出错误, 或者其他不可预测的行为. 取决于编译器的实现.

#include <utility>

enum CaseOptions : int {
  Option1 = 1,
  Option2 = 2,
  Option3 = 3,
};

int foo(CaseOptions i) {
  switch (i) {
    case Option1:
      return 1;
    case Option2:
      return 2;
    case Option3:
      return 3;
    default:
      std::unreachable();
  }
}

查看汇编结果

foo(CaseOptions):
        mov     eax, edi
        ret

可以看到优化结果是直接返回了i的值. 因为编译器认为default分支是不可达的. 这种函数如果遇到了[1-3]之外的输入则会导致不可预测的结果.

int main(int, const char*[]) {
  std::cout << foo(Option1) << std::endl;
  std::cout << foo(static_cast<CaseOptions>(4)) << std::endl;
}

运行结果

1
685604928 <-- 未定义行为

假设(assume)

C++23之前,不同编译器有不同的方式来标记假设. 例如:

  • __builtin_assume (Clang)
  • __assume (MSVC and ICC)
  • if (expr) {} else { __builtin_unreachable(); } (GCC)

C++23统一了这些标记, 引入了std::assume函数.

[[assume(expr)]]

这个标记用来告诉编译器一些前提条件, 以便优化代码.

未加修饰符号

int len(const char* ptr) {
    if (ptr == nullptr) {
        throw "oops";
    }
    return 0;
}

编译生成

.LC0:
        .string "oops"
len(char const*):
        test    rdi, rdi
        je      .L3
        xor     eax, eax
        ret
len(char const*) [clone .cold]:
.L3:
        push    rax
        mov     edi, 8
        call    __cxa_allocate_exception
        xor     edx, edx
        mov     esi, OFFSET FLAT:_ZTIPKc
        mov     QWORD PTR [rax], OFFSET FLAT:.LC0
        mov     rdi, rax
        call    __cxa_throw

assume之后:

int len(const char* ptr) {
    [[assume(ptr != nullptr)]];
    if (ptr == nullptr) {
        throw "oops";
    }
    return 0;
}

编译生成

len(char const*):
        xor     eax, eax
        ret

可以看出整个异常处理代码被优化掉了.

进一步参考:

Unicode已命名字符转义

C++23之前, 字符转义只能用于ASCII字符.

auto c {U'\u0100'}; // {Latin Capital Letter A with macron}

C++23引入了已命名字符转义

auto c {U'\N{Latin Capital Letter A with macron}'};

example

#include <cassert>
#include <codecvt>
#include <iostream>
#include <locale>
#include <string>

int main() {
  auto c{U'\u0100'};  // {LATIN CAPITAL LETTER A WITH MACRON}
  auto nc{U'\N{LATIN CAPITAL LETTER A WITH MACRON}'};
  assert(c == nc);
  std::u32string str;

  str.push_back(c);
  str.push_back(nc);
  str.push_back('\n');

  std::locale::global(std::locale(""));

  std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;

  std::cout << converter.to_bytes(str);
}

行拼接符('\')前的空白字符

在C++23以前, 如果行拼接符\后有空白字符, 则会出现未定义的行为.

  • Clang & GCC: 会忽略空白字符并警告, 输出1
  • MSVC: 不会忽略空白字符, 输出2

C++23中, 行拼接符后的空白字符会被忽略.

#include <iostream>

int main() {
  int i = 1
      // 反斜杠后面有个空白符(space) \
    + 1
      ;
  std::cout << i << '\n';
}
  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
现代 C 语言是指 C11/C17 标准之后的 C 语言版本。其核心特性包括了多线程、原子操作、泛型和 stdatomic 库等。下面具体解析一下现代 C 语言核心特性。 1. 多线程 多线程是现代 C 语言中最重要的特性之一。多线程使得程序可以同时执行多个任务,从而提高程序性能。现代 C 语言通过 POSIX 线程库和 Windows 线程库实现多线程编程。POSIX 线程库是 POSIX 标准中定义的线程库,可以跨平台使用。Windows 线程库是 Windows 操作系统中的线程库,可以在 Windows 系统上使用。 2. 原子操作 原子操作是现代 C 语言中的另一个重要特性。原子操作可以保证多线程环境下的数据、变量等在并发访问时不会出错。现代 C 语言提供了一组原子操作的 API。这些 API 包括了原子加、原子减、原子赋值、原子与、原子或等。这些原子操作可以在不同的平台上使用。 3. 泛型 现代 C 语言引入了泛型的概念。泛型允许程序员在不同的数据类型上编写通用的代码。这使得现代 C 语言可以实现更加通用的数据结构和算法。现代 C 语言中的泛型使用了类型参数化的技术,即把某些代码中的类型抽象出来作为参数。 4. stdatomic 库 stdatomic 库是现代 C 语言中的一个库,它提供了原子类型和原子操作等特性。stdatomic 库使用了 C11 标准中的 _Atomic 关键字来定义原子类型。stdatomic 库中包含了原子加、原子减、原子赋值等操作函数,这些操作可以在多线程环境下执行,保证数据的正确性。 总之,现代 C 语言核心特性包括了多线程、原子操作、泛型和 stdatomic 库等。这些特性使得现代 C 语言可以更好地支持并发编程、泛型编程等。开发者可以充分利用这些特性来提高程序性能和可维护性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值