掌握C++17核心特性
Posted 凌桓丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了掌握C++17核心特性相关的知识,希望对你有一定的参考价值。
文章目录
语言特性
折叠表达式
可变参数模板编程中我们通常会面临一个问题,即参数包不能直接展开,需要通过一些特殊的方法来实现,因此在一定程度上提高了使用的复杂度。
在 C++ 17 中引入了折叠表达式,其以二元运算符对形参包进行规约,简化了对参数包的展开。
折叠表达式的实例化按以下方式展开成表达式 e
:
-
一元右折叠
(E 运算符 ...)
成为(E1 运算符 (... 运算符 (EN-1 运算符 EN)))
-
一元左折叠
(... 运算符 E)
成为(((E1 运算符 E2) 运算符 ...) 运算符 EN)
-
二元右折叠
(E 运算符 ... 运算符 I)
成为(E1 运算符 (... 运算符 (EN−1 运算符 (EN 运算符 I))))
-
二元左折叠
(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
可读性也不高,很难第一时间看出对应的成员是上面,同时也存在越界的风险。 - 对于方法三,比起上面两种大大提升了代码的可读性,但是仍然存在一个问题,就是需要提前定义好对应的成员变量,然后再通过
tie
将tuple
成员解包到对应变量中,使用起来还是有点麻烦。
为了使上面这种代码简洁、可读性高,在 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 中支持在 if
和 switch
中使用初始化语句,此时代码就可以写成这样:
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) 类型的实例的对象。
其主要使用场景如下:
- 替代万能类型(如传统的
void*
),并提供更好的类型安全和效率。 - 避免小对象的动态内存分配。
使用示例如下:
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++基础二类和对象(上篇)(10000字掌握C++类核心内容)