C++17新特性

C++17

语言特性

类模板的模板参数推导

自动模板参数推导很像它为函数完成的方式,但现在包括类构造函数。

template <typename T = float>
struct MyContainer {
  T val;
  MyContainer() : val{} {}
  MyContainer(T val) : val{val} {}
  // ...
};
MyContainer c1 {1}; // OK MyContainer<int>
MyContainer c2; // OK MyContainer<float>

用 auto 声明非类型模板参数

遵循auto的推导规则,在尊重允许类型[*]的非类型模板形参列表的同时,模板实参可以从其实参的类型中推导出来:

template <auto... seq>
struct my_integer_sequence {
  // 一些实现 ...
};

// 显式传递类型 `int` 作为模板参数。
auto seq = std::integer_sequence<int, 0, 1, 2>();
// 推导类型是`int`.
auto seq2 = my_integer_sequence<0, 1, 2>();

* - 例如,您不能使用double作为模板参数类型,这也使得使用auto的推导无效。

折叠表达式

折叠表达式对二元运算符执行模板参数包的折叠。

  • (... op e)(e op ...)形式的表达式,其中op是折叠运算符,e是未展开的参数包,称为一元折叠。
  • (e1 op ... op e2)形式的表达式,其中op是折叠运算符,称为二元折叠。 e1e2是未扩展的参数包,但二者不能共存。
template <typename... Args>
bool logicalAnd(Args... args) {
    // 二元折叠
    return (true && ... && args);
}
bool b = true;
bool& b2 = b;
logicalAnd(b, b2, true); // == true
template <typename... Args>
auto sum(Args... args) {
    // 一元折叠
    return (... + args);
}
sum(1.0, 2.0f, 3); // == 6.0

从花括号初始化列表中自动推导的新规则

与统一初始化语法一起使用时对自动推导的更改。以前,自动x {3};推导出一个std::initializer_list<int>,现在推导出为int

auto x1 {1, 2, 3}; // 错误:不是单个元素
auto x2 = {1, 2, 3}; // x2 是 std::initializer_list<int>
auto x3 {3}; // x3 是 int
auto x4 {3.0}; // x4 是 double

constexpr lambda

编译时lambda使用constexpr

auto identity = [](int n) constexpr { return n; };
static_assert(identity(123) == 123);
constexpr auto add = [](int x, int y) {
  auto L = [=] { return x; };
  auto R = [=] { return y; };
  return [=] { return L() + R(); };
};

static_assert(add(1, 2)() == 3);
constexpr int addOne(int n) {
  return [n] { return n + 1; }();
}

static_assert(addOne(1) == 2);

lambda按值捕获this

在 lambda 的环境中捕获this在以前只是按找引用。有问题的一个例子是使用回调的异步代码,它需要一个对象可用,可能已经过了它的生命周期。 *this (C++17) 现在将复制当前对象,而this(C++11) 继续通过引用捕获。

struct MyObj {
  int value {123};
  auto getValueCopy() {
    return [*this] { return value; };
  }
  auto getValueRef() {
    return [this] { return value; };
  }
};
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
mo.value = 321;
valueCopy(); // 123
valueRef(); // 321

内联变量

inline说明符可以应用于变量以及函数。内联声明的变量与内联声明的函数具有相同的语义。

// 使用编译器资源管理器的反汇编示例。
struct S { int x; };
inline S x1 = S{321}; // mov esi, dword ptr [x1]
                      // x1: .long 321

S x2 = S{123};        // mov eax, dword ptr [.L_ZZ4mainE2x2]
                      // mov dword ptr [rbp - 8], eax
                      // .L_ZZ4mainE2x2: .long 123

它也可以用来声明和定义一个静态成员变量,这样它就不需要在源文件中初始化。

struct S {
  S() : id{count++} {}
  ~S() { count--; }
  int id;
  static inline int count{0}; // 在类中声明并将计数初始化为 0
};

嵌套命名空间

使用命名空间解析运算符创建嵌套的命名空间定义。

namespace A {
  namespace B {
    namespace C {
      int i;
    }
  }
}

上面的代码可以这样写:

namespace A::B::C {
  int i;
}

结构化绑定

解构初始化的提议,允许编写auto [ x, y, z ] = expr;其中expr的类型是一个类似元组的对象,其元素将绑定到变量 x、y 和 z(此构造声明)。类元组对象包括std::tuplestd::pairstd::array和聚合结构。

using Coordinate = std::pair<int, int>;
Coordinate origin() {
  return Coordinate{0, 0};
}

const auto [ x, y ] = origin();
x; // == 0
y; // == 0
std::unordered_map<std::string, int> mapping {
  {"a", 1},
  {"b", 2},
  {"c", 3}
};

// 通过引用解构。
for (const auto& [key, value] : mapping) {
  // Do something
}

带有初始化的选择语句

新版本的ifswitch语句简化了常见的代码模式并帮助用户保持范围紧凑。

{
  std::lock_guard<std::mutex> lk(mx);
  if (v.empty()) v.push_back(val);
}
// vs.
if (std::lock_guard<std::mutex> lk(mx); v.empty()) {
  v.push_back(val);
}
Foo gadget(args);
switch (auto s = gadget.status()) {
  case OK: gadget.zip(); break;
  case Bad: throw BadFoo(s.message());
}
// vs.
switch (Foo gadget(args); auto s = gadget.status()) {
  case OK: gadget.zip(); break;
  case Bad: throw BadFoo(s.message());
}

constexpr if

编写根据编译时条件实例化的代码。

template <typename T>
constexpr bool isIntegral() {
  if constexpr (std::is_integral<T>::value) {
    return true;
  } else {
    return false;
  }
}
static_assert(isIntegral<int>() == true);
static_assert(isIntegral<char>() == true);
static_assert(isIntegral<double>() == false);
struct S {};
static_assert(isIntegral<S>() == false);

UTF-8 字符字面量

u8开头的字符文字是char类型的字符文字。 UTF-8 字符文字的值等于其 ISO 10646 代码点值。

char x = u8'x';

枚举的直接列表初始化

现在可以使用大括号语法初始化枚举。

enum byte : unsigned char {};
byte b {0}; // OK
byte c {-1}; // ERROR
byte d = byte{1}; // OK
byte e = byte{256}; // ERROR

fallthrough, nodiscard, maybe_unused 属性

C++17 引入了三个新属性:[[fallthrough]][[nodiscard]][[maybe_unused]]

  • [[fallthrough]]向编译器表明 switch 语句中的失败是预期的行为。

    switch (n) {
      case 1: [[fallthrough]]
        // ...
      case 2:
        // ...
        break;
    }
    
  • [[nodiscard]]当函数或类具有此属性并且其返回值被丢弃时发出警告。

    [[nodiscard]] bool do_something() {
      return is_success; // true for success, false for failure
    }
    
    do_something(); // 警告:忽略了 'bool do_something()' 返回值
                    // 声明了属性 'nodiscard'
    
  • [[maybe_unused]]向编译器指示变量或参数可能未使用并且是预期的。

    void my_callback(std::string msg, [[maybe_unused]] bool error) {
      // 不在乎 `msg` 是否是错误消息,只需将其记录下来。
      log(msg);
    }
    

标准库

std::variant

类模板std::variant表示类型安全的union。在任何给定时间,std::variant的一个实例都拥有它的一种替代类型的值(它也可能是没有值)。

std::variant<int, double> v{ 12 };
std::get<int>(v); // == 12
std::get<0>(v); // == 12
v = 12.0;
std::get<double>(v); // == 12.0
std::get<1>(v); // == 12.0

std::optional

类模板std::optional管理一个可选的包含值,即一个可能存在也可能不存在的值。optional的一个常见用例是可能失败的函数的返回值。

std::optional<std::string> create(bool b) {
  if (b) {
    return "Godzilla";
  } else {
    return {};
  }
}

create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// 可以返回一个null,空值
if (auto str = create(true)) {
  // ...
}

std::any

用于任何类型的单个值的类型安全容器。

std::any x {5};
x.has_value() // == true
std::any_cast<int>(x) // == 5
std::any_cast<int&>(x) = 10;
std::any_cast<int>(x) // == 10

std::string_view

对字符串的非拥有引用。用于在字符串之上提供一层抽象(例如,用于解析)。

就像其它语言的string一样,不可被更改。

// Regular strings.
std::string_view cppstr {"foo"};
// Wide strings.
std::wstring_view wcstr_v {L"baz"};
// Character arrays.
char array[3] = {'b', 'a', 'r'};
std::string_view array_v(array, std::size(array));
std::string str {"   trim me"};
std::string_view v {str};
v.remove_prefix(std::min(v.find_first_not_of(" "), v.size()));
str; //  == "   trim me"
v; // == "trim me"

std::invoke

使用参数调用Callable对象。Callable对象的示例是std::functionstd::bind,其中对象可以与常规函数类似地调用。

template <typename Callable>
class Proxy {
  Callable c;
public:
  Proxy(Callable c): c(c) {}
  template <class... Args>
  decltype(auto) operator()(Args&&... args) {
    // ...
    return std::invoke(c, std::forward<Args>(args)...);
  }
};
auto add = [](int x, int y) {
  return x + y;
};
Proxy<decltype(add)> p {add};
p(1, 2); // == 3

std::apply

使用参数元组调用Callable对象。

auto add = [](int x, int y) {
  return x + y;
};
std::apply(add, std::make_tuple(1, 2)); // == 3

std::filesystem

新的std::filesystem库提供了一种在文件系统中操作文件、目录和路径的标准方法。

在这里,如果有可用空间,则将一个大文件复制到临时路径:

const auto bigFilePath {"bigFileToCopy"};
if (std::filesystem::exists(bigFilePath)) {
  const auto bigFileSize {std::filesystem::file_size(bigFilePath)};
  std::filesystem::path tmpPath {"/tmp"};
  if (std::filesystem::space(tmpPath).available > bigFileSize) {
    std::filesystem::create_directory(tmpPath.append("example"));
    std::filesystem::copy_file(bigFilePath, tmpPath.append("newFile"));
  }
}

std::byte

新的std::byte类型提供了一种将数据表示为字节的标准方式。在charunsigned char上使用std::byte的好处是它不是字符类型,也不是算术类型;而唯一可用的运算符重载是按位运算。

std::byte a {0};
std::byte b {0xFF};
int i = std::to_integer<int>(b); // 0xFF
std::byte c = a & b;
int j = std::to_integer<int>(c); // 0

请注意,std::byte 只是一个枚举,并且由于枚举的直接列表初始化,使得枚举的大括号初始化成为可能。

map和set的拼接

移动节点和合并容器,无需昂贵的副本、移动或堆分配/解除分配的开销。

将元素从一个map移动到另一个map

std::map<int, string> src {{1, "one"}, {2, "two"}, {3, "buckle my shoe"}};
std::map<int, string> dst {{3, "three"}};
dst.insert(src.extract(src.find(1))); // 以低代价移动 { 1, "one" } 从 `src` 到 `dst`.
dst.insert(src.extract(2)); // 以低代价移动 { 2, "two" } 从 `src` 到 `dst`.
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };

插入整个集合:

std::set<int> src {1, 3, 5};
std::set<int> dst {2, 4, 5};
dst.merge(src);
// src == { 5 }
// dst == { 1, 2, 3, 4, 5 }

插入比容器寿命更长的元素:

auto elementFactory() {
  std::set<...> s;
  s.emplace(...);
  return s.extract(s.begin());
}
s2.insert(elementFactory());

更改map的key

std::map<int, string> m {{1, "one"}, {2, "two"}, {3, "three"}};
auto e = m.extract(2);
e.key() = 4;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }

并行算法

许多STL算法,例如复制、查找和排序方法,开始支持并行执行策略:seqparpar_unseq,它们转换为“sequentially”、“parallel”和“parallel unsequenced”。

std::vector<int> longVector;
// 使用并行执行策略查找元素
auto result1 = std::find(std::execution::par, std::begin(longVector), std::end(longVector), 2);
// 使用顺序执行策略对元素进行排序
auto result2 = std::sort(std::execution::seq, std::begin(longVector), std::end(longVector));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值