当前位置: 首页 > article >正文

C++17 新增特性总结: 核心语言特性

语言特性

结构化绑定(Structured Bindings)

结构化绑定允许你将一个结构体, 元组或数组的元素直接绑定到命名变量上, 下面是几个 C++17 结构化绑定的示例:

// struct binding
struct Item {
  int id;
  std::string name;
};

Item item = {1, "item"};
auto [id, name] = item;

std::map<std::string, int> scores = {
    {"Alice", 85}, {"Bob", 90}, {"Charlie", 78}};
// 绑定std::pair
for (const auto& [key, value] : scores) {
  std::cout << key << ": " << value << '\n';
}

// 绑定tuple
auto t1 = std::tuple<int, int, int>{1, 2, 3};
auto [x, y, z] = t1;

带初始化的 if/switch

在下面的例子中, if语句中声明了一个局部变量it, 并且在if语句块中使用这个变量.

// if 语句中初始化变量
if (auto it = scores.find("Bob"); it != scores.end()) {
  std::cout << "找到了元素 Bob" << std::endl;
}

// 在 switch 语句中初始化变量
switch (auto score = scores["Bob"]; score / 10) {
  case 9:
    std::cout << "Bob 的成绩等级是 A" << std::endl;
    break;
  case 8:
    std::cout << "Bob 的成绩等级是 B" << std::endl;
    break;
  case 7:
    std::cout << "Bob 的成绩等级是 C" << std::endl;
    break;
  default:
    std::cout << "Bob 的成绩等级未知" << std::endl;
}

inline 变量的增强

C++17 增加了两个新的特性:

  1. 在头文件中用inline修饰变量, 而不用担心重复定义的问题.
  2. 在类中声明static inline成员变量时, 可以直接初始化.
#pragma once

class sample {
  inline static int default_value = 47; /* C++ 17支持内联定义, 无需分开初始化 */
};

inline int global_uuid = 0; /* C++ 17新特性, 被多个文件include也不会出现冲突 */

更多类初始化的内容: C++ 类成员初始化发展历程(从 C++11 到 C++20)

聚合扩展

在 C++17 之前, 从其他结构派生的结构会禁用聚合初始化, 开发者必须定义构造函数. C++17 引入聚合体扩展后, 聚合体可以拥有基类, 支持使用花括号进行列表初始化, 并且可以省略一些嵌套括号, 使代码更加简洁.

struct Data {
  std::string name;
  double value;
};
struct MoreData : Data {
  bool done;
};
MoreData md1{{"test1", 6.778}, false}; // 使用嵌套括号进行初始化
MoreData md2{"test1", 6.778, false};   // 当基类或子对象只接受一个值时, 可省略嵌套括号

mandatory copy elision or parsing unmaintainable code

在 C++17 之前, 复制省略(Copy Elision)是一种编译器优化手段, 允许编译器在某些情况下省略对象的复制或移动操作, 但这并非强制要求, 代码仍然需要保证复制构造函数和移动构造函数的可调用性. 而在 C++17 中, 在特定的初始化场景下, 复制省略成为强制规则, 即使类没有定义复制或移动构造函数, 相关的初始化操作也能正常进行.

强制复制省略的场景:

  • 返回临时对象: 当函数按值返回一个临时对象(prvalue)时, 强制复制省略会发生, 直接在调用处初始化目标对象, 而不会进行复制或移动操作.
  • 直接初始化: 使用临时对象直接初始化另一个对象时, 也会触发强制复制省略.
class MCE {
  int x;

  public:
  MCE() { std::cout << "Constructor called" << std::endl; }
  // 显式删除复制构造函数
  MCE(const MCE&) = delete;
  // 显式删除移动构造函数
  MCE(MCE&&) = delete;
  ~MCE() { std::cout << "Destructor called" << std::endl; }
};

auto createObject = []() { return MCE(); };

MCE mce2 = createObject(); // 返回临时对象
MCE mce1 = MCE();          // 直接初始化

lambda 表达式增强

constexpr lambda
auto l1 = [](auto x) constexpr { return x * x; };  // OK
constexpr int ci1 = l1(2);
lambda 捕获 *this
class C {
 private:
  std::string name;

 public:
  void foo() {
    auto l1 = [*this] { std::cout << name << '\n'; };
  }
};

命名空间增强

  1. 嵌套的命名空间
// before
namespace Company {
namespace Project {
namespace Component {
class Foo;
}  // namespace Component
}  // namespace Project
}  // namespace Company

// c++ 17
namespace Company::Project::Component {
class Foo;
}

新增 attributes

  • [[nodiscard]]: 用于标记函数返回值不应该被忽略. 如果忽略带有 [[nodiscard]] 属性的函数返回值, 编译器通常会给出警告, 有助于避免因忽略重要返回值而可能导致的逻辑错误.

    [[nodiscard]] int openFile(const char* path) {
      int fd = -1;
      // ...
      return fd;
    }
    
  • [[fallthrough]]: 用于在 switch 语句中显式表明允许从一个 case 标签穿透下降到下一个 case 标签, 避免编译器给出警告.

    void comment(int place) {
      switch (place) {
        case 1:
          std::cout << "非常 ";
          [[fallthrough]];
        case 2:
          std::cout << "好\n";
          break;
        default:
          std::cout << "一般\n";
          break;
      }
    }
    
  • [[maybe_unused]]: 用于告诉编译器某个变量, 函数参数, 类成员等可能不会被使用, 从而避免编译器给出未使用变量的警告.

    void processData([[maybe_unused]] int value, double factor) {
      // 这里没有使用 value 参数,但由于 [[maybe_unused]] 标记,不会产生警告
      double result = factor * 2;
      std::cout << "Result: " << result << std::endl;
    }
    

utf-8 character literals

从 C++11 开始有string 类型的字面量, C++17 引入了 utf-8 字符串字面量.

// u8 char literals
char c = u8'a';          // ASCII
char16_t ch = u'猫';     // 中文字符
char32_t emoji = U'🍌';  // emoji

noexcept specifications are part of the type system

在 C++17 中, noexcept关键字被引入了类型系统中, 意味着是否有noexcept将影响函数类型.

void f1(int x) noexcept;
void f2(int x);

f1f2是不同的类型.

class Base {
  public:
  virtual void foo() noexcept;
};
class Derived : public Base {
  public:
  void foo() override;  // ERROR: does not override
};

表达式求值顺序

C++17 中, 规定了表达式求值顺序如下:

  1. 如下形式的表达式中, e1先于e2求值
    • e1[e2]
    • e1.e2
    • e1.*e2
    • e1->*e2
    • e1 << e2
    • e1 >> e2
  2. 赋值语句中, 右侧的表达式先求值
    • e2 = e1
    • e2 += e1
    • e2 *= e1
  3. new Type(e)操作符中, 先分配内存, 再计算参数e

样例代码

i = ++i + 2;            // well-defined
i = i++ + 2;            // undefined behavior until C++17
a[i] = i++;             // undefined behavior until C++17
std::cout << i << i++;  // undefined behavior until C++17
i = ++i + i++;          // undefined behavior
int n = ++i + i;        // undefined behavior

relaxed enum initialization from integral types

是 C++17 引入的一项新特性,主要用于改进枚举类型的初始化方式。在 C++17 之前,对于具有固定底层类型的枚举,使用整数值进行直接列表初始化是不允许的,而 C++17 放宽了这一限制。

// 无作用域枚举,指定底层类型为char
enum Enum1 : char {};
Enum1 i1{42};     // OK since C++17 (ERROR before C++17)
Enum1 i2 = 42;    // 仍然错误
Enum1 i3(42);     // 仍然错误
Enum1 i4 = {42};  // 仍然错误

// 有作用域枚举,默认底层类型
enum class Enum2 { mon, tue, wed, thu, fri, sat, sun };
Enum2 s1{0};     // OK since C++17 (ERROR before C++17)
Enum2 s2 = 0;    // 仍然错误
Enum2 s3(0);     // 仍然错误
Enum2 s4 = {0};  // 仍然错误

// 有作用域枚举,指定底层类型为char
enum class Enum3 : char { mon, tue, wed, thu, fri, sat, sun };
Enum3 s5{0};  // OK since C++17 (ERROR before C++17)

Enum3 s6 = 0;    // 仍然错误
Enum3 s7(0);     // 仍然错误
Enum3 s8 = {0};  // 仍然错误

// 无作用域枚举,未指定底层类型
enum Enum4 { bit1 = 1, bit2 = 2, bit3 = 4 };
Enum4 f1{0};  // 仍然错误

// 尝试进行窄化转换,仍然错误
enum Enum5 : char {};
Enum5 i5{42.2};  // 仍然错误

改进auto直接初始化行为

auto类型推到的行为在 C++17 中进行了修改:

// C++ 17 之前
auto at1{42};    // 类型为: std::initializer_list<int>
auto at2{1,2,3}; // OK: 类型为: std::initializer_list<int>
// C++17
auto at1{42};       // 类型为int
auto at2{1, 2, 3};  // Error

单个参数的 static_assert

// C++17 之前
static_assert(std::is_default_constructible<T>::value, "class C: elements must be default-constructible");
// C++17
static_assert(std::is_default_constructible_v<T>);

预处理宏 __has_include

检查特定的头文件是否能被包含. 也就是检查系统是否存在该头文件.

#if __has_include(<filesystem>)
#include <filesystem>
#define HAS_FILESYSTEM 1
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
#define HAS_FILESYSTEM 1
#define FILESYSTEM_IS_EXPERIMENTAL 1
#elif __has_include("filesystem.hpp")
#include "filesystem.hpp"
#define HAS_FILESYSTEM 1
#define FILESYSTEM_IS_EXPERIMENTAL 1
#else
#define HAS_FILESYSTEM 0
#endif

http://www.kler.cn/a/571701.html

相关文章:

  • C#:LINQ学习笔记01:LINQ基础概念
  • 力扣1594. 矩阵的最大非负积
  • 爬蟲動態IP代理與數據採集穩定性
  • 【文生图】Win10环境借助基于ComfyUI的图狗2.3.1抢先体验阿里万相wan2.1
  • 【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(未成功版)
  • OpenHarmony文件管理子系统
  • XMOS推出“免开发固件方案”将数字接口音频应用的开发门槛大幅降低
  • angular实现nodejs增删改查
  • 前端2025
  • 开源之夏经验分享|Koupleless 社区黄兴抗:在开源中培养工程思维
  • Spring Boot Gradle 项目中使用 @Slf4j 注解
  • 基于微信小程序的竞赛报名系统设计与实现
  • 能做期权交易的标的物有哪些?
  • IO进程线程2
  • vscode设置不自动打开项目【超详细图解】
  • 深度学习R8周:RNN实现阿尔兹海默症(pytorch)
  • C++学习(七)(标准库+STL(iotstream公司,日期/时间,器皿,算法,迭代器,多线程))
  • 深入理解网络通信中的关键概念:HTTP、TCP与Socket的关系及TCP的可靠性保障
  • Google C++ 开源风格指南
  • 用AI学安卓游戏开发1——控制小球上下左右移动2