前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【翻译】C++17的新特性简介

【翻译】C++17的新特性简介

作者头像
ZifengHuang
发布2020-07-29 16:34:24
2.9K0
发布2020-07-29 16:34:24
举报
文章被收录于专栏:未竟东方白未竟东方白

上一篇翻译了C++14的新特性简介,这篇就是Anthony Calandra在https://github.com/AnthonyCalandra/modern-cpp-features/blob/master/CPP17.md 中对C++17重要的新特性的简介。原文中有些地方写得不是很好理解所以对其做了少量修改

C++14的内容主要是对C++11的补充,被称为C++11的完整版。相比之下C++17的新内容要多很多,是完善的再完善

C++20由于好多特性都没被支持好,本身又属于非常大的一次变革,所以那部分就暂不翻译了

新特性一览

语言新特性

  • 类模板的模板参数推断
  • 用auto来声明非类型的模板参数
  • 折叠表达式
  • auto对花括号初始化的新推断规则
  • Lambda的常量表达式形式
  • Lambda可以值捕获this了
  • 内联变量
  • 嵌套的命名空间定义
  • 结构化绑定
  • 带有初始化器的条件语句
  • 常量表达式if
  • UTF-8的字符字面量
  • 枚举的直接列表初始化
  • [[fallthrough]], [[nodiscard]], [[maybe_unused]]属性

标准库新特性

  • std::variant
  • std::optional
  • std::any
  • std::string_view
  • std::invoke
  • std::apply
  • std::filesystem
  • std::byte
  • 拼接map和set
  • 并行算法

类模板的模板参数推断(Template argument deduction for class templates)

对类模板的模板参数的推断就像编译器对函数参数的推导一样,只是如今可以用在模板类的构造中了

代码语言:javascript
复制
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来声明非类型的模板参数(Declaring non-type template parameters with auto)

遵循着auto的推断规则,同时也遵循那些被允许的类型*的非类型模板参数(C++Primer16.1中有介绍)列表,模板参数可以从它的实参的类型中推断出来了

代码语言:javascript
复制
template <auto... seq>
struct my_integer_sequence {
  // Implementation here ...
};

// Explicitly pass type `int` as template argument.
auto seq = std::integer_sequence<int, 0, 1, 2>();
// Type is deduced to be `int`.
auto seq2 = my_integer_sequence<0, 1, 2>();

* ——例如:由于你不能用一个double类型作为模板参数的类型,因而也不能对其进行auto推断

折叠表达式(Folding expressions)

折叠表达式是为了优化C++11引入的参数包而生的。折叠表达式可以在一个二元运算符上对类参数包进行折叠

  • 当一个形如(... op e)(e op ...)的表达式,op是一个二元运算符而e是一个未展开的参数包时,称此为一元折叠
  • 当一个形如(e1 op ... op e2)的表达式,op是一个二元运算符,称此为二元折叠。这里e1e2的两者之一是未展开的参数包,且注意两个op需要时是相同的运算符
代码语言:javascript
复制
template <typename... Args>
auto sum(Args... args) {
    // Unary folding.
    return (... + args);
}
sum(1.0, 2.0f, 3); // == 6.0
代码语言:javascript
复制
template <typename... Args>
bool logicalAnd(Args... args) {
    // Binary folding.
    return (true && ... && args);
}
bool b = true;
bool& b2 = b;
logicalAnd(b, b2, true); // == true

auto对花括号初始化的新推断规则(New rules for auto deduction from braced-init-list)

当使用统一初始化运算符(花括号)时auto的推断发生了一些变化。在之前auto x {3};会推断出一个std::initializer_list<int>,如今会推断出int

代码语言:javascript
复制
auto x1 {1, 2, 3}; // error: not a single element
auto x2 = {1, 2, 3}; // x2 is std::initializer_list<int>
auto x3 {3}; // x3 is int
auto x4 {3.0}; // x4 is double

Lambda的常量表达式形式(constexpr lambda)

想要得到编译期的Lambda的话可以使用constexpr关键字

代码语言:javascript
复制
auto identity = [](int n) constexpr { return n; };
static_assert(identity(123) == 123);
代码语言:javascript
复制
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);
代码语言:javascript
复制
constexpr int addOne(int n) {
  return [n] { return n + 1; }();
}

static_assert(addOne(1) == 2);

Lambda可以值捕获this了(Lambda capture this by value)

之前的时候在Lambda中捕获的this只能是引用形式的。一个有问题的例子是对于引用捕获,Lambda要求其对象必须确实存在,但有可能到了调用的时候已经超过了目标对象的生命周期。现在我们可以使用*this(C++17)得到对当前对象的一个拷贝,而用this(C++11)则会继续捕获目标的引用

代码语言:javascript
复制
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 variables)

inline关键字现在可以像用在函数上一样用在变量上了。一个被声明内联的变量会得到和内联函数一样的语义

代码语言:javascript
复制
// Disassembly example using compiler explorer.
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

这也可以被用来声明或定义一个静态成员变量,这样它就可以不用必须在源文件中初始化了

代码语言:javascript
复制
struct S {
  S() : id{count++} {}
  ~S() { count--; }
  int id;
  static inline int count{0}; // declare and initialize count to 0 within the class
};

嵌套的命名空间定义(Nested namespaces)

可以用命名空间解析运算符(在命名空间后用作用域运算符)来定义一个嵌套的命名空间了

代码语言:javascript
复制
namespace A {
  namespace B {
    namespace C {
      int i;
    }
  }
}
// vs.
namespace A::B::C {
  int i;
}

结构化绑定(Structured bindings)

这是一个对非结构化初始化的建议,就是现在允许了当expr是一个类似tuple的对象时可以用auto [ x, y, z ] = expr;来初始化,对象中的元素会被绑定到x,y和z上

类似tuple的对象包括std::tuple, std::pair, std::array和其他一些聚合结构

代码语言:javascript
复制
using Coordinate = std::pair<int, int>;
Coordinate origin() {
  return Coordinate{0, 0};
}

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

// Destructure by reference.
for (const auto& [key, value] : mapping) {
  // Do something with key and value
}

带有初始化器的条件语句(Selection statements with initializer)

if和switch的新版本条件语句简化了常见的代码模式并帮助用户进一步保持代码紧凑

代码语言:javascript
复制
{
  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);
}
代码语言:javascript
复制
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());
}

常量表达式if(constexpr if)

可以编写一些依据编译期状态初始化的代码了

代码语言:javascript
复制
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的字符字面量(UTF-8 character literals)

一个以u8开头的字符字面量是char类型的。UTF-8字符字面量的值与它的ISO 10646标准的字符点值相同。

代码语言:javascript
复制
char x = u8'x';

枚举的直接列表初始化(Direct list initialization of enums)

枚举现在可以用花括号直接初始化了

代码语言:javascript
复制
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]]属性(fallthrough, nodiscard, maybe_unused attributes)

C++17还带来了三个新的属性:[[fallthrough]], [[nodiscard]][[maybe_unused]]

  • [[fallthrough]] 向编译器指明从某个switch的case滑下(也就是没有break打断)是设计中的行为
代码语言:javascript
复制
switch (n) {
  case 1: [[fallthrough]]
    // ...
  case 2:
    // ...
    break;
}
  • [[nodiscard]] 会在拥有这个属性的类或函数的返回值被忽略时提出警告
代码语言:javascript
复制
[[nodiscard]] bool do_something() {
  return is_success; // true for success, false for failure
}

do_something(); // warning: ignoring return value of 'bool do_something()',
                // declared with attribute 'nodiscard'
代码语言:javascript
复制
// Only issues a warning when `error_info` is returned by value.
struct [[nodiscard]] error_info {
  // ...
};

error_info do_something() {
  error_info ei;
  // ...
  return ei;
}

do_something(); // warning: ignoring returned value of type 'error_info',
                // declared with attribute 'nodiscard'
  • [[maybe_unused]] 向编译器指明某个变量或参数可能不会被使用到是设计中的行为
代码语言:javascript
复制
void my_callback(std::string msg, [[maybe_unused]] bool error) {
  // Don't care if `msg` is an error message, just log it.
  log(msg);
}

std::variant

标准库模板类std::variant(变体/变种)代表了一个类型安全的union。一个std::variant的实例每个时刻都只保留候选类型中的一个值(当然也可以是无值的),就像联合一样

代码语言:javascript
复制
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(可选项)维护了一个可选的包含值,例如,一个可能存在也可能不存在的值。一个常见的可选项的使用情形就是作为可能失败的函数的返回值

代码语言:javascript
复制
std::optional<std::string> create(bool b) {
  if (b) {
    return "Godzilla";
  } else {
    return {};
  }
}

create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// optional-returning factory functions are usable as conditions of while and if
if (auto str = create(true)) {
  // ...
}

std::any

这是对任何类型的单值的一个类型安全的容器

代码语言:javascript
复制
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

是对一个字符串的非拥有的引用,可以运用成员函数自由地改变观测这个字符串的方式,构造速度很快但是无法修改这个字符串。对在字符串上提供一个抽象很有用(例如进行字符串分析)

代码语言:javascript
复制
// 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));
代码语言:javascript
复制
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可调用对象。可调用对象就是类似 std::functionstd::bind那样的可以类似普通的函数那样被调用的对象

代码语言:javascript
复制
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

可以用tuple来作为参数包装调用一个可调用对象

代码语言:javascript
复制
auto add = [](int x, int y) {
  return x + y;
};
std::apply(add, std::make_tuple(1, 2)); // == 3

std::filesystem

新的文件系统库std::filesystem提供了在文件系统中控制多文件,多目录,多路径的标准方法

就像下面例子,在有可用空间的情况下将一个大文件拷贝到一个临时路径中

代码语言:javascript
复制
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提供了以位格式表示数据的标准方法。std::byte比起char和unsigned char的好处在于它不是一种字符类型也不是一种算术类型,因此它只有可用的重载运算符只有位运算符

代码语言:javascript
复制
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只是一个枚举enum而已,多亏了枚举类型的直接列表初始化特性才能向上面一样优雅地使用它

拼接map和set(Splicing for maps and sets)

现在可以在避免拷贝,移动,堆内存分配的高额代价下移动结点和合并容器了

从一个map移动元素到另一个map中:

代码语言:javascript
复制
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))); // Cheap remove and insert of { 1, "one" } from `src` to `dst`.
dst.insert(src.extract(2)); // Cheap remove and insert of { 2, "two" } from `src` to `dst`.
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };

插入一整个set进去:

代码语言:javascript
复制
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 }

插入比容器自身寿命还长的元素:

代码语言:javascript
复制
auto elementFactory() {
  std::set<...> s;
  s.emplace(...);
  return s.extract(s.begin());
}
s2.insert(elementFactory());

改变一个map元素的key:

代码语言:javascript
复制
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" } }

并行算法(Parallel algorithms)

许多STL算法例如copy,find,sort等,开始支持并行执行策略(parallel execution policies):串行seq,并行par,无序并行par_unseq

代码语言:javascript
复制
std::vector<int> longVector;
// Find element using parallel execution policy
auto result1 = std::find(std::execution::par, std::begin(longVector), std::end(longVector), 2);
// Sort elements using sequential execution policy
auto result2 = std::sort(std::execution::seq, std::begin(longVector), std::end(longVector));
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 未竟东方白 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档