掌握C++17核心特性

Posted 凌桓丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了掌握C++17核心特性相关的知识,希望对你有一定的参考价值。

文章目录


语言特性

折叠表达式

可变参数模板编程中我们通常会面临一个问题,即参数包不能直接展开,需要通过一些特殊的方法来实现,因此在一定程度上提高了使用的复杂度。

在 C++ 17 中引入了折叠表达式,其以二元运算符对形参包进行规约,简化了对参数包的展开。

折叠表达式的实例化按以下方式展开成表达式 e

  1. 一元右折叠 (E 运算符 ...) 成为 (E1 运算符 (... 运算符 (EN-1 运算符 EN)))

  2. 一元左折叠 (... 运算符 E) 成为 (((E1 运算符 E2) 运算符 ...) 运算符 EN)

  3. 二元右折叠 (E 运算符 ... 运算符 I) 成为 (E1 运算符 (... 运算符 (EN−1 运算符 (EN 运算符 I))))

  4. 二元左折叠 (I 运算符 ... 运算符 E) 成为 ((((I 运算符 E1) 运算符 E2) 运算符 ...) 运算符 EN)

(其中 N 是包展开中的元素数量)

这里举一个一元左折叠的例子:

template<typename... Args>
bool all(Args... args)  return (... && args); 
 
bool b = all(true, true, true, false);

// 展开结果为 return ((true && true) && true) && false;


类模板实参推导

为了实例化一个类模板,需要知晓但不需要指定每个模板实参。在下列语境中,编译器会从初始化器的类型推导缺失的模板实参:

  • 任意指定变量及变量模板初始化的声明:
std::pair p(2, 4.5);     // 推导出 std::pair<int, double> p(2, 4.5);
std::tuple t(4, 3, 2.5); // 同 auto t = std::make_tuple(4, 3, 2.5);
std::less l;             // 同 std::less<void> l;
  • new 表达式:
template<class T> struct A

    A(T, T);
;
auto y = new A1, 2; // 分配的类型是 A<int>
  • 函数式转型表达式:
auto lck = std::lock_guard(mtx);     // 推导出 std::lock_guard<std::mutex>
std::copy_n(vi1, 3,
    std::back_insert_iterator(vi2)); // 推导出 std::back_insert_iterator<T>,
                                     // 其中 T 是容器 vi2 的类型
std::for_each(vi.begin(), vi.end(),
    Foo([&](int i) ...));          // 推导出 Foo<T>,其中 T 是独有的 lambda 类型


结构化绑定

结构化绑定即绑定指定名称到初始化器的子对象或元素。类似引用,结构化绑定是既存对象的别名。不同于引用的是,结构化绑定的类型不必为引用类型。

那么它有什么作用呢?这里我举一个场景,假设我们需要在一个函数中返回多种不同类型的值,在之前的版本主要有以下三种写法:

  • 定义一个结构体,以结构体返回。
  • tuple 返回,通过 std::get() 访问 tuple
  • tuple 返回,通过 std::tie() 解包 tuple
struct Info
	int x;
	double y;
	string z;
;

Info getInfoByStruct()

	return Info 1, 2.000, "hello" ;


tuple<int, double, string> getInfoByTuple()

	return make_tuple(2, 3.0000, "world");


int main()

	//写法一:定义结构体
	Info info = getInfoByStruct();
	cout << info.x << " " << info.y << " " << info.z << endl;
	
	//写法二:返回一个tuple,通过get访问
	tuple<int, double, string> res = getInfoByTuple();
	cout << get<0>(res) << " " << get<1>(res) << " " << get<2>(res) << endl;

	//写法三:返回一个tuple,通过tie解包tuple
	int x;
	double y;
	string z;
	tie(x, y, z) = getInfoByTuple();
	cout << x << " " << y << " " << z << endl;

  • 对于方法一来说,如果这个类型的返回值不是很常用,那么定义一个结构体不仅麻烦,而且如果这种情况非常多的时候,大量的结构体很容易引起阅读者的混乱。
  • 对于方法二,通过 get<>() 使用下标的方式访问 tuple 可读性也不高,很难第一时间看出对应的成员是上面,同时也存在越界的风险。
  • 对于方法三,比起上面两种大大提升了代码的可读性,但是仍然存在一个问题,就是需要提前定义好对应的成员变量,然后再通过 tietuple 成员解包到对应变量中,使用起来还是有点麻烦。

为了使上面这种代码简洁、可读性高,在 C++ 17 中就引入了结构化绑定,使用方式如下:

int main()

	auto [x, y, z] = getInfoByTuple();
	cout << x << " " << y << " " << z << endl;

此时我们可以看见,代码的简洁度、可读性都大大提高了。


inline 变量

在老版本的 C++ 中,如果我们需要定义一个全局变量,则首先要将变量定义在 cpp 文件中,然后在通过 extern 关键字来告诉编译器这个变量已经在其他地方定义过了。

C++ 17 有了内联变量,就可以完美的解决这个问题。我们可以直接将全局变量定义在头文件中,而不用担心出现重定义。

//头文件

class Test 
public:
    inline static const int value = 4;
;

//或者

class Test 
public:
    static const int value; 
;
inline int const A::value = 4;


if 和 switch 语句中的初始化器

在 C++ 17 中支持在 ifswitch 中使用初始化语句,此时代码就可以写成这样:

if (bool res = find(key); res == true)

	//执行查找正确逻辑


switch (int score = get_score(); score %= 10)

    case 1: /*...*/ break;
    case 2: /*...*/ break;
    case 3: /*...*/ break;
    case 4: /*...*/ break;
    case 5: /*...*/ break;
    case 6: /*...*/ break;

此时就能够更好的去约束一些临时变量的作用域,同时也能使得代码更加简洁。


库特性

apply

template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t);

apply 以参数的元组调用可调用(Callable)对象 f 。(简单点说就是展开元组,使其作为函数的参数)

元组不必是 std::tuple ,可以为任何支持 std::get 和 std::tuple_size 的类型所替代;特别是可以用 std::array 和 std::pair 。

例如:

int add(int first, int second)  return first + second; 
 
template<typename T>
T add_generic(T first, T second)  return first + second; 
 
auto add_lambda = [](auto first, auto second)  return first + second; ;
 
template<typename... Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> const& theTuple)

    std::apply
    (
        [&os](Ts const&... tupleArgs)
        
            os << '[';
            std::size_t n0;
            ((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);
            os << ']';
        , theTuple
    );
    return os;

 
int main()

    // OK
    std::cout << std::apply(add, std::pair(1, 2)) << '\\n';
 
    // 错误:无法推导函数类型
    // std::cout << std::apply(add_generic, std::make_pair(2.0f, 3.0f)) << '\\n'; 
 
    // OK
    std::cout << std::apply(add_lambda, std::pair(2.0f, 3.0f)) << '\\n'; 
 
    // 进阶示例
    std::tuple myTuple(25, "Hello", 9.31f, 'c');
    std::cout << myTuple << '\\n';


make_from_tuple

template <class T, class Tuple>
constexpr T make_from_tuple(Tuple&& t);

构造 T 类型对象,以元组 t 的元素为构造函数的参数。

用法如下:

struct Foo 
    Foo(int first, float second, int third) 
        std::cout << first << ", " << second << ", " << third << "\\n";
    
;
 
int main()

   auto tuple = std::make_tuple(42, 3.14f, 0);
   std::make_from_tuple<Foo>(std::move(tuple));


any

any 可保有任何可复制构造 (CopyConstructible) 类型的实例的对象。

其主要使用场景如下:

  1. 替代万能类型(如传统的 void*),并提供更好的类型安全和效率。
  2. 避免小对象的动态内存分配。

使用示例如下:

int main()

    std::cout << std::boolalpha;
 
    // any 类型
    std::any a = 1;
    std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\\n';
    a = 3.14;
    std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\\n';
    a = true;
    std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\\n';
 
    // 有误的转型
    try
    
        a = 1;
        std::cout << std::any_cast<float>(a) << '\\n';
    
    catch (const std::bad_any_cast& e)
    
        std::cout << e.what() << '\\n';
    
 
    // 拥有值
    a = 1;
    if (a.has_value())
    
        std::cout << a.type().name() << '\\n';
    
 
    // 重置
    a.reset();
    if (!a.has_value())
    
        std::cout << "no value\\n";
    
 
    // 指向所含数据的指针
    a = 1;
    int* i = std::any_cast<int>(&a);
    std::cout << *i << "\\n";


optional

在 C++ 中经常会有一个让人苦恼的问题,当我们需要在某个结构中查询一个对象,并将其作为返回值时,如果查询不到,则难以去表述查询失败,例如下面的代码:

template<typename K, typename V>
class TestMap

public:
	V get(const K& key)
	
		if (_set.count(key) == 0)
		
            //难以表述查询失败,由于不是指针类型,所以无法返回nullptr,只能约定一个空值
			return V();
		
		return _set[key];
	

prvate:
	unordered_map<K, V> _set;
;

由于其不是指针类型,所以无法返回 nullptr,只能提前约定一个值作为错误结果。

在这种情况下,通常我们会采用以下两种方法来解决:

  • 返回一个 V*,但是此时又会存在内存管理的问题,因此需要使用智能指针。
  • 返回一个 pair(V, bool),也是最常用的方法,但同时会存在构造对象的成本。

上面的两种方法都较为麻烦,因此在 C++ 14 中引入 optional 来完美的解决这个问题。

optional 管理一个可选的容纳值,既可以存在也可以不存在的值。与其他手段,如 std::pair<T,bool> 相比, optional 良好地处理构造开销高昂的对象,并更加可读。

这里我们改造一下上面的代码

template<typename K, typename V>
class TestMap

public:
	optional<V> get(const K& key)
	
		if (_set.find(key) == _set.end())
		
			return nullopt;
		
		return _set[key];
	
private
	unordered_map<K, V> _set;
;


file_system

在老版本的 C++ 中,并没有封装对文件系统的操作,因此我们通常需要调用一些第三方库(如 boost)或者操作系统提供的 api 来实现对应的功能。

在 C++ 17 中,正式将 file_system 加入进标准库,其定义的成员与函数如下:

#include <compare>
 
namespace std::filesystem 
  // 路径
  class path;
 
  // 路径非成员函数
  void swap(path& lhs, path& rhs) noexcept;
  size_t hash_value(const path& p) noexcept;
 
  // 文件系统错误
  class filesystem_error;
 
  // 目录条目
  class directory_entry;
 
  // 目录迭代器
  class directory_iterator;
 
  // 目录迭代器的范围访问
  directory_iterator begin(directory_iterator iter) noexcept;
  directory_iterator end(directory_iterator) noexcept;
 
  // 递归目录迭代器
  class recursive_directory_iterator;
 
  // 递归目录迭代器的范围访问
  recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept;
  recursive_directory_iterator end(recursive_directory_iterator) noexcept;
 
  // 文件状况
  class file_status;
 
  struct space_info 
    uintmax_t capacity;
    uintmax_t free;
    uintmax_t available;
 
    friend bool operator==(const space_info&, const space_info&) = default;
  ;
 
  // 枚举
  enum class file_type;
  enum class perms;
  enum class perm_options;
  enum class copy_options;
  enum class directory_options;
 
  using file_time_type = chrono::time_point<chrono::file_clock>;
 
  // 文件系统操作
  path absolute(const path& p);
  path absolute(const path& p, error_code& ec);
 
  path canonical(const path& p);
  path canonical(const path& p, error_code& ec);
 
  void copy(const path& from, const path& to);
  void copy(const path& from, const path& to, error_code& ec);
  void copy(const path& from, const path& to, copy_options options);
  void copy(const path& from, const path& to, copy_options options,
            error_code& ec);
 
  bool copy_file(const path& from, const path& to);
  bool copy_file(const path& from, const path& to, error_code& ec);
  bool copy_file(const path& from, const path& to, copy_options option);
  bool copy_file(const path& from, const path& to, copy_options option,
                 error_code& ec);
 
  void copy_symlink(const path& existing_symlink, const path& new_symlink);
  void copy_symlink(const path& existing_symlink, const path& new_symlink,
                    error_code& ec) noexcept;
 
  bool create_directories(const path& p);
  bool create_directories(const path& p, error_code& ec);
 
  bool create_directory(const path& p);
  bool create_directory(const path掌握C++17核心特性

c++17比较string_view和string时的歧义

CSS 核心样式、H5 + C3 新特性

C++基础二类和对象(上篇)(10000字掌握C++类核心内容)

C++基础二类和对象(上篇)(10000字掌握C++类核心内容)

C++基础二类和对象(上篇)(10000字掌握C++类核心内容)