C++17新特性

构造函数模板推导

// before c++17
pair<int, double> p(1, 2.2); 

// c++17 自动推导
pair p(1, 2.2); 
vector v = {1, 2, 3}; 

结构化绑定

读取值

std::tuple<int, double> func() {
    return std::tuple(1, 2.2);
}

int main() {
    auto[i, d] = func(); //是C++11的tie吗?更高级
    cout << i << endl;
    cout << d << endl;
}

//==========================
void f() {
    map<int, string> m = {
      {0, "a"},
      {1, "b"},  
    };
    for (const auto &[i, s] : m) {
        cout << i << " " << s << endl;
    }
}

// ====================
int main() {
    std::pair a(1, 2.3f);
    auto[i, f] = a;
    cout << i << endl; // 1
    cout << f << endl; // 2.3f
    return 0;
}

通过结构化绑定改变对象的值

int main() {
    std::pair a(1, 2.3f);
    auto& [i, f] = a;
    i = 2;
    cout << a.first << endl; // 2 
}

结构化绑定不能应用于constexpr

constexpr auto[x, y] = std::pair(1, 2.3f); // compile error, C++20可以

绑定数组和结构体等

int array[3] = {1, 2, 3};
auto [a, b, c] = array;
cout << a << " " << b << " " << c << endl;

// 注意这里的struct的成员一定要是public的
struct Point {
    int x;
    int y;
};
Point func() {
    return {1, 2};
}
const auto [x, y] = func();

实现自定义类的结构化绑定

class Entry {
public:
    void Init() {
        name_ = "name";
        age_ = 10;
    }

    std::string GetName() const { return name_; }
    int GetAge() const { return age_; }
private:
    std::string name_;
    int age_;
};

// 重载了C++标准库中的std::get
// if constexpr 确保了在编译时就确定分支,避免运行时开销。
template <size_t I>
auto get(const Entry& e) {
    if constexpr (I == 0) return e.GetName();
    else if constexpr (I == 1) return e.GetAge();
}

// 为了让Entry类能够被当作元组一样对待,特别是用于结构化绑定和获取元组大小和类型信息
namespace std {
	// 声明Entry有2个元素
    template<> struct tuple_size<Entry> : integral_constant<size_t, 2> {}; 
    // 第0个元素类型为std::string
    template<> struct tuple_element<0, Entry> { using type = std::string; };
    // // 第1个元素类型为int
    template<> struct tuple_element<1, Entry> { using type = int; };
}

int main() {
    Entry e;
    e.Init();
    auto [name, age] = e;
    cout << name << " " << age << endl; // name 10
    return 0;
}

if-switch语句初始化

C++17之前

int a = GetValue();
if (a < 101) 
    cout << a;

C++17

// if (init; condition)

if (int a = GetValue()); a < 101) {
    cout << a;
}

string str = "Hi World";
if (auto [pos, size] = pair(str.find("Hi"), str.size()); pos != string::npos) {
    std::cout << pos << " Hello, size is " << size;
}

可以尽可能约束作用域,让代码更简洁,可读性可能略有下降

内联变量

C++类的静态成员变量在头文件中是不能初始化的,但是有了内联变量,就可以达到此目的

// header file
struct A {
    static const int value;  
};
inline int const A::value = 10;

// ==========或者========
struct A {
    inline static const int value = 10;
}

折叠表达式

使可变参数模板编程更方便

template <typename ... Ts>
auto sum(Ts ... ts) {
    return (ts + ...);
}
int a {sum(1, 2, 3, 4, 5)}; // 15
std::string a{"hello "};
std::string b{"world"};
cout << sum(a, b) << endl; // hello world
语法分类折叠展开
一元右折叠(unary right fold)( pack op … )E1 op (… op (EN-1 op EN))
一元左折叠(unary left fold)( … op pack )((E1 op E2) op …) op EN
二元右折叠(binary right fold)( pack op … op init )E1 op (… op (EN−1 op (EN op I)))
二元左折叠(binary left fold)( init op … op pack )(((I op E1) op E2) op …) op EN

op代表运算符:下列 32 个二元运算符之一:+ - * / % ^ & | = < > << >> += -= = /= %= ^= &= |= <<= >>= == != <= >= && || , . ->*。在二元折叠中,两个运算符必须相同。

pack代表参数包:含有未展开的形参包且在顶层不含优先级低于转型(正式而言,是 转型表达式)的运算符的表达式。

init代表初始值:不含未展开的形参包且在顶层不含优先级低于转型(正式而言,是 转型表达式)的运算符的表达式注意开闭括号也是折叠表达式的一部分。

这里的括号是必需的。但是,圆括号和省略号(…)不必用空格分隔。

  • 初始值在右边的为右折叠,展开之后从右边开始折叠。
  • 初始值在左边的为左折叠,展开之后从左边开始折叠。
  • 不指定初始值的为一元折叠表达式
  • 指定初始值的为二元折叠表达式

将一元折叠用于长度为零的包展开时,只能使用下列运算符:

  1. 逻辑与(&&)。空包的值是 true
  2. 逻辑或(||)。空包的值是 false
  3. 逗号运算符(,)。空包的值是 void()
// 向左折叠
template<typename... Args>
auto sum(Args... args) {
    return (... + args);
}

template<typename... Args>
bool all_true(Args... args) {
    return (... && args);
}

// 向右折叠
template<typename... Args>
std::string concat(Args... args) {
    return (args + ...);
}

template<typename T, typename... Args>
T max(T first, Args... args) {
    return (first > ... > args ? first : (... > args ? args : first));
}

int main() {
    std::cout << max(3, 5, 1, 9, 2) << std::endl; // 输出:9
}

constexpr lambda表达式

constexpr lambda表达式,可以用于在编译期进行计算。

如果lambda体内的计算逻辑仅依赖于编译时已知的值,那么整个lambda可以在编译时期就被执行,其结果可以用于初始化constexpr变量或作为模板参数等。

使用场景

  1. 编译时常量计算:当需要在编译时计算某个值,且该计算逻辑较为复杂,不适合直接用简单的表达式描述时,可以使用constexpr lambda。

  2. 模板元编程:在模板元编程中,有时需要定义一些小型的计算逻辑,这些逻辑可以在编译时确定,使用constexpr lambda可以保持代码的清晰度和可读性。

  3. 算法和容器初始化:在初始化std::array、std::vector等容器,或者在实现算法时,如果初始化值或计算逻辑可以是编译时确定的,constexpr lambda可以用来提供这些值。
    注意点

  4. 纯函数:constexpr lambda必须是纯函数,即其行为不能有副作用,且结果只依赖于其输入参数。

  5. 捕获列表:constexpr lambda不能捕获非constexpr的变量,也不能有默认捕获(= default或= [&]),因为这些都可能导致运行时的状态依赖。

  6. 返回类型:constexpr lambda的返回类型需要是字面量类型或者是支持constexpr构造的类型,不能是引用类型。

  7. 函数体:lambda函数体内的所有操作必须是编译时可计算的,包括调用的函数也应该是constexpr的。

#include <iostream>
#include <array>

int main() {
    constexpr auto square = 
    [](int x) constexpr -> int 
    { return x * x; };

    constexpr std::array<int, 5> squares =
     {square(0), square(1), square(2), square(3), square(4)};
    
    for (const auto& val : squares) {
        std::cout << val << " ";
    }
    // 输出:0 1 4 9 16
    return 0;
}

namespace嵌套

namespace A {
    namespace B {
        namespace C {
            void func();
        }
    }
}

// c++17,更方便更舒适
namespace A::B::C {
    void func();)
}

__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
#include <iostream>
 
int main()
{
#if __has_include(<cstdio>)
    printf("c program");
#endif
 
#if __has_include("iostream")
    std::cout << "c++ program" << std::endl;
#endif
 
    return 0;
}

在lambda表达式用*this捕获对象副本

当你在捕获列表中使用this关键字时,你实际上是捕获了当前对象的一个指针或者引用,这取决于你如何捕获this。
如果你想捕获对象的一个副本(即当前对象的一个拷贝),你需要明确地捕获*this,这样做是为了在lambda内部拥有一个独立的对象副本,而非原始对象的引用或指针。这样做的好处是可以避免在lambda内部修改影响到原始对象的状态,尤其是在多线程环境下更为安全。

struct A {
    int a;
    void func() {
        auto f = [*this] { // 这里
            cout << a << endl;
        };
        f();
    }  
};
int main() {
    A a;
    a.func();
    return 0;
}

要访问类的某些成员但又不想直接操作原对象

#include <iostream>

class MyClass {
public:
    MyClass(int data) : data_(data) {}

    void process() {
        // 捕获当前对象的一个副本(*this),这样lambda就有自己的一个MyClass实例
        auto lambda = [copy = *this]() {
            copy.data_ *= 2; // 修改的是副本的data_
            std::cout << "In lambda: " << copy.data_ << std::endl;
        };

        lambda(); // 调用lambda,输出副本的数据
        std::cout << "Outside lambda: " << data_ << std::endl; // 原对象的data_未改变
    }

private:
    int data_;
};

int main() {
    MyClass obj(10);
    obj.process();
    return 0;
}

当在lambda内部修改copy.data_时,不会影响到外部obj对象的data_成员,因为在lambda内操作的是一个独立的副本。输出将会显示lambda内部修改的是副本的值,而原始对象的值保持不变。

新增Attribute

我们可能平时在项目中见过__declspec, attribute , #pragma指示符,使用它们来给编译器提供一些额外的信息,来产生一些优化或特定的代码,也可以给其它开发者一些提示信息。

struct A { short f[3]; } __attribute__((aligned(8)));

void fatal() __attribute__((noreturn));

// [[carries_dependency]] 让编译期跳过不必要的内存栅栏指令
// [[noreturn]] 函数不会返回
// [[deprecated]] 函数将弃用的警告

[[noreturn]] void terminate() noexcept;
[[deprecated("use new func instead")]] void func() {}

[[fallthrough]]

用在switch中提示可以直接落下去,不需要break,让编译期忽略警告

switch (i) {}
    case 1:
        xxx; // warning
    case 2:
        xxx; 
        [[fallthrough]];      // 警告消除
    case 3:
        xxx;
       break;
}
[[nodiscard]]

修饰的内容不能被忽略,可用于修饰函数,标明返回值一定要被处理

[[nodiscard]] int func();
void F() {
    func(); // warning 没有处理函数返回值
}
[[maybe_unused]]

提示编译器修饰的内容可能暂时没有使用,避免产生警告

void func1() {}
[[maybe_unused]] void func2() {} // 警告消除
void func3() {
    int x = 1;
    [[maybe_unused]] int y = 2; // 警告消除
}

u8

添加在常量字符串前,表示这是一个utf-8编码的字符串:

char str[] = u8"你好,世界!";

聚合初始化

在C++17之前,结构体使用大括号初始化需写构造函数,现在不需要了

struct x
{
	int a;
	float b;
};
struct y : x
{
	std::string c;
};

y z = { { 10, 20 }, "hello world" };
y z{ { 10, 20 }, "hello world" };

字符串转换

新增from_chars函数和to_chars函数
更方便的处理字符串和数字之间的转换

#include <charconv>
#include <iostream>

int main()
{
	std::string str("1234");
	int val;
	//ec为错误码,详情查看xerrc.h,ptr成员为未转换或非数字的字符
	if(auto[ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), val);
	   ec == std::errc())
	{
		std::cout << val;//1234
	}

	val = 321;
	if(auto[ptr, ec] = std::to_chars(str.data(), str.data() + str.size(), val);
	   ec == std::errc())
	{
		std::cout << str;//3214
	}

	auto format = std::chars_format::general;
	double pi = 0.f;
	str = "3.14";
	if(auto[ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), pi, format);
	   ec == std::errc())
	{
		std::cout << pi;//3.14
	}
	return 0;
}

std::variant

多变的、可变的复合类型
实现类似union的功能,但却比union更高级,举个例子union里面不能有string这种类型,但std::variant却可以,还可以支持更多复杂类型,如map等

#include <variant>
#include <iostream>

int main()
{
	std::variant<int, std::string> var("123");
    std::variant<int, std::string> v("hello");
    std::cout << v.index();

    v = 123;
    std::cout << v.index();
    v = "321";
    try
    {
            std::string str = std::get<std::string>(v);//str=321
            int i = std::get<0>(v);//抛异常
    }
    catch (std::bad_variant_access& e)
    {
    
    }
	return 0;
}

variant的第一个类型一般要有对应的构造函数,否则编译失败

struct A {
    A(int i){}  
};
int main() {
    std::variant<A, int> var; // 编译失败
}

解决方案:
为struct A提供一个默认构造函数

#include <variant>

struct A {
    A(int i){} // 保留原有的构造函数
    // 添加移动构造函数和移动赋值运算符
    A(A&&) noexcept = default;
    A& operator=(A&&) noexcept = default;
};

int main() {
    std::variant<A, int> var{A(42)}; // 现在可以编译了,使用A的构造函数初始化
    return 0;
}

使用std::monostate

#include <variant>

struct A {
    A(int i){} // 保持原有的构造函数不变
};

int main() {
    std::variant<std::monostate, A, int> var; // 现在可以编译了
    // 默认初始化时,var将持有std::monostate,表明没有具体类型被选定

	std::variant<std::monostate, A, int> var{A{42}};
	std::variant<std::monostate, A, int> var{42};
    return 0;
}

in-place构造相关工具

主要在构造容器内的对象时避免不必要的拷贝和移动。从而提高性能和效率

std::in_place

是一个类型的别名,指向一个struct in_place_t类型的对象。
当使用std::in_place构造一个std::optional或std::variant时,它指示构造应该在已有的内存位置上发生,而不是先创建一个临时对象再移动到目位置

#include <optional>
#include <variant>
#include <string>
#include <iostream>

int main() {
    // 使用 std::in_place 构造一个 std::optional<int>
    std::optional<int> opt = std::in_place, 42; // C++20
    
    // 使用 std::in_place 构造一个 std::variant<int, std::string>
    std::variant<int, std::string> var = std::in_place_type<int>, 100;

    std::cout << "Optional contains: " << *opt << std::endl;
    std::cout << "Variant contains: " << std::get<int>(var) << std::endl;

    return 0;
}
std::in_place_type

允许指定具体的类型来构造std::variant中的对象。它接受一个类型作为模板参数,并可以传递到std::variant的构造函数中,以明确构造哪个替代类型

#include <variant>
#include <string>
#include <iostream>

int main() {
    // 使用 std::in_place_type 指定构造 std::variant 中的 std::string 类型
    std::variant<int, std::string> var = std::in_place_type<std::string>, "Hello";

    std::cout << "Variant contains: " << std::get<std::string>(var) << std::endl;

    return 0;
}
std::in_place_index

用于std::variant,允许通过索引指定要构造的类型

#include <variant>
#include <string>
#include <iostream>

int main(){
	// 指定构造 var中的第二个类型
	std::variant<int, std::string> var = std::in_place_index<1>, "world";
	std::cout<<"contains:"<<std::get<1>(var)<<std::endl;
	return 0;
}

std::optional

我们有时候可能会有需求,让函数返回一个对象,如下:

struct A {};
A func() {
    if (flag) return A();
    else {
        // 异常情况下,怎么返回异常值呢,想返回个空呢
    }
}
创建std::optional的方式
//初始化为空
std::optional<int> emptyInt;
std::optional<double> emptyDouble = std::nullopt;

//直接用有效值初始化
std::optional<int> intOpt{10};
std::optional intOptDeduced{10.0}; // auto deduced

//使用make_optional
auto doubleOpt = std::make_optional(10.0);
auto complexOpt = std::make_optional<std::complex<double>>(3.0, 4.0);

//使用in_place
std::optional<std::complex<double>> complexOpt{std::in_place, 3.0, 4.0};
std::optional<std::vector<int>> vectorOpt{std::in_place, {1, 2, 3}};

//使用其它optional对象构造
auto optCopied = vectorOpt;
std::optional<int> findStudent(const std::map<std::string, int> & students, const std::string & name)
{
	if (students.find(name) == students.end())
	{
		return std::nullopt;
	}
	return students[name];
}

//使用
auto studentId = findStudent(students, "Bob");

std::unique_ptr<double> nonCopyableReturn()
{
	std::unique_ptr<double> p = nullptr;
	return {p}; //强制产生拷贝,将产生编译错误,因为unique_ptr为non-copyable类型
	// return p; //move语义,unique_ptr可以move,编译通过。
}

访问存储值

// operator* 和 operator->
// operator* 返回内部存储对象的引用,operator->返回指向内部存储对象的指针
// 如果没有有效值,则行为未定义
std::optional<std::string> opt{"abc"};
std::cout << "content is " << *opt << ", size is " << opt->size() << std::endl;

// value()
// 返回内部存储对象的值,当optional为空时抛出std::bad_optional_access异常
try
{
	std::cout << "content is " << opt.value() << std::endl;
}
catch(const std::bad_optional_access & e)
{
	std::cout << e.what() << std::endl;
}

// value_or(defaultValue)
// optional有有效值时返回有效值,否则返回默认值
std::optional<int> optInt(100);
std::cout << "value is " << optInt.value_or(10) << std::endl;

修改存储值以及存储对象的生命周期

对于一个已经存在的optional对象,通过调用emplace, reset, swap, operator=,可以将其中存储的值修改掉。如果调用operator=或者reset将optional对象赋值为nullopt,若之前的optional存储有有效值,则存储类型的析构函数将被调用。除此之外,每次optional内部存储对象被重置,之前对象的析构函数都会被调用。

class tStudent
{
public:
	explicit tStudent(std::string str)
	: m_name(str)
	{}
	~tStudent() = default;
}// 构造空的optional
std::optional<tStudent> optStudent;
// 构造名字为“Bob”的tStudent对象存储在optional对象中
optStudent.emplace("Bob");
// 相当于
// optStudent = tStudent{"Bob"};
// "Bob"对象析构,构造"Steve"
optStudent.emplace("Steve")
// "Steve"对象析构
optStudent.reset();

在比较大小时,std::nullopt总小于存储有效值的optional对象

std::optional<int> int1(1);
std::optional<int> int2(10);
std::optional<int> int3;

std::cout << std::boolalpha;
std::cout << (int1 < int2) << std::endl; // true
std::cout << (int2 > int1) << std::endl; // true
std::cout << (int3 == std::nullopt) << std::endl; // true
std::cout << (int3 < int1) << std::endl; // true

内存

使用optional包装原始类型意味着需要存储原始类型的空间和额外的boolean flag,因此optional对象将占有更多的内存空间。此外,optional对象的内存排列须遵循与内部对象一致的内存对齐准则。

template <typename T>
class optional
{
	bool _initialized;
	std::aligned_storage_t<sizeof(T), alignof(T)> _storage;
public: 
// operations 
};

假如sizeof(double) = 8,sizeof(int) = 4,则:
std::optional optDouble; // sizeof(optDouble) = 16
std::optional optInt; // sizeof(optInt) = 8

std::any

any可以存储任何类型的单个值

int main() { // c++17可编译
    std::any a = 1;
    cout << a.type().name() << " " << std::any_cast<int>(a) << endl;
    a = 2.2f;
    cout << a.type().name() << " " << std::any_cast<float>(a) << endl;
    if (a.has_value()) {
        cout << a.type().name();
    }
    a.reset();
    if (a.has_value()) {
        cout << a.type().name();
    }
    a = std::string("a");
    cout << a.type().name() << " " << std::any_cast<std::string>(a) << endl;
    return 0;
}

std::apply

std::apply可以将tuple展开作为函数的参数传入

基本示例
#include <iostream>
#include <tuple>
#include <functional>

void print(int a, int b, int c) {
    std::cout << a << ", " << b << ", " << c << std::endl;
}

int main() {
    std::tuple<int, int, int> t = {1, 2, 3};
    std::apply(print, t);
    return 0;
}
返回类型
template< class F, class Tuple >
constexpr decltype(auto) apply( F&& f, Tuple&& t );

std::apply的返回类型是函数F应用于元组t的元素后的返回类型。如果F返回void,那么std::apply也返回void。否则,它返回F的返回类型。

#include <tuple>
#include <iostream>

// 定义一个函数,接受两个整数参数,返回它们的和
int add(int a, int b) {
    return a + b;
}

int main() {
    // 创建一个元组
    std::tuple<int, int> t = std::make_tuple(1, 2);

    // 使用std::apply将元组的元素作为参数传递给add函数
    int sum = std::apply(add, t);

    std::cout << "The sum is " << sum << std::endl;  // 输出 "The sum is 3"

    return 0;
}
遍历元组
std::tuple<int, std::string, float> t1(10, "Test", 3.14);

std::apply([](auto&&... args) {
    ((std::cout << args << '\n'), ...);
}, t1);

lambda函数接受一个可变参数包

实现元组的序列化

序列化是将数据结构或对象状态转换为可以存储或传输的格式的过程。在序列化元组时,我们需要处理元组的每个元素,并将它们转换为一个字符串。

std::tuple<int, std::string, float> t1(10, "Test", 3.14);

std::string s = std::apply([](auto&&... args) {
    return (std::to_string(args) + ...);
}, t1);

处理函数和参数的打包和解包

在并行和并发编程中,需要将函数和它的参数打包起来,传递给另一个线程或任务。
因为线程或任务的执行上下文与创建它们的上下文不同,我们需要一种方式来在新的上下文中调用函数。

#include <iostream>
#include <tuple>
#include <utility>
#include <thread>

void print(int a, int b, int c) {
    std::cout << a << ' ' << b << ' ' << c << '\n';
}

int main() {
    auto args = std::make_tuple(1, 2, 3);
    std::thread t([&args] { std::apply(print, args); });
    t.join();
    // Output: 1 2 3
}

std::make_from_tuple

可以将tuple展开作为构造函数参数

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));
}

std::string_view

通常我们传递一个string时会触发对象的拷贝操作,大字符串的拷贝赋值操作会触发堆内存分配,很影响运行效率,有了string_view就可以避免拷贝操作,平时传递过程中传递string_view即可

void func(std::string_view stv)
{
	cout << stv << endl;
}

int main(void) {
    std::string str = "Hello World";
    std::cout << str << std::endl;

    std::string_view stv(str.c_str(), str.size());
    cout << stv << endl;
    func(stv);
    return 0;
}
std::string_view和std::string的运算符操作
#include<iostream>
#include<string>
#include<string_view>
 
int main()
{
	std::string str1 = "hello";
	std::string_view sv1 = " world";
	//使用+号运算符时,必须将string_view转化为const char*
	auto it = str1 + sv1.data();
	//使用append追加字符串不会出错
	auto it2 = str1.append(sv1);
	std::cout << it2 << std::endl;
 
	return 0;
}

警告:返回字符串的函数应该返回const std::string&或std::string,但不应该返回std::string_view。返回std::string_view会带来使返回的std::string_view无效的风险,例如当它指向的字符串需要重新分配时。

警告:将const std:string&或std::string_view存储为类的数据成员需要确保它们指向的字符串在对象的生命周期内保持有效状态,存储std::string更安全。

查找函数使用
string replace_post(string_view src, string_view new_post)
{
    // 找到点的位置
    auto pos = src.find(".") + 1;
    // 取出点及点之前的全部字符,string_view的substr会返回一个
    // string_view对象,所以要取data()赋值给string对象
    string s1 = src.substr(0, pos).data();
 
    // 加上新的后缀
    return s1 + new_post.data();
}
int main()
{
    string_view sv = "abcdefg.xxx";
    string s = replace_post(sv, "yyy");
    cout << sv << " replaced post by yyy result is:" << s << endl;
    return 0;
}

输出:

abcdefg.xxxyyy

string s1 = src.substr(0, pos).data();返回后s1还是 “abcdefg.xxx”,
std::string_view内部只是简单地封装原始字符串的起始位置和结束位置, 相当于给字符串设置了一个观察窗口,用户只能看到通过窗口能看到的那部分数据.
data()成员返回的是char*的指针, 是std::string_view内部字符串的起始位置.
所以其表现再来的行为跟C字符串一样了, 直到遇到空字符串才结束。

std::string_view和临时字符串

std::string_view并不拥有其指向内容的所有权
如果拥有者提前释放了,你还在使用这些内容,那会出现内存问题
下面列出一些典型的问题情况:

std::string_view sv = std::string{"hello world"}; 
string_view foo() {
    std::string s{"hello world"};
    return string_view{s};
}
auto id(std::string_view sv) { return sv; }
 
int main() {
    std::string s = "hello";
    auto sv = id(s + " world"); 
}

as_const

as_const可以将左值转成const类型,返回的是cosnt引用类型:

#include <iostream>
#include <utility>
using namespace std;
 
int main()
{
	string str = "str";
	const string& constStr = as_const(str);
	cout<<&str<<endl;
	cout<<&constStr<<endl;
	return 0;
}
 
运行程序输出:
0x64fde0
0x64fde0
 
可见通过as_const返回的常量引用对象constStr与源对象str是同一个对象
但是constStr是常量对象,不能对其进行赋值操作

file_system

C++17正式将file_system纳入标准中,提供了关于文件的大多数功能,基本上应有尽有

namespace fs = std::filesystem;
fs::create_directory(dir_path);
fs::copy_file(src, dst, fs::copy_options::skip_existing);
fs::exists(filename);
fs::current_path(err_code);
#include <iostream>
#include <filesystem>
#include <fstream>

namespace fs = std::filesystem;

int main() {
    // 设置测试目录和文件的路径
    fs::path dir_path = "test_dir";
    fs::path file_path = dir_path / "test_file.txt"; // 使用 / 来拼接路径

    // 创建一个目录 (底层可能使用 mkdir 系统调用)
    if (!fs::exists(dir_path)) {
        fs::create_directory(dir_path);
    }

    // 创建并写入一个文件 (底层可能使用 open, write 系统调用)
    std::ofstream file(file_path);
    file << "Hello, Filesystem!";
    file.close();

    // 检查文件是否存在 (底层可能使用 stat 系统调用)
    if (fs::exists(file_path)) {
        std::cout << "File created successfully.\n";
    }

    // 读取文件大小 (底层可能使用 stat 系统调用)
    std::cout << "File size: " << fs::file_size(file_path) << " bytes.\n";

    // 重命名文件 (底层可能使用 rename 系统调用)
    fs::path new_file_path = dir_path / "renamed_file.txt";
    fs::rename(file_path, new_file_path);

    // 遍历目录 (底层可能使用 opendir, readdir, closedir 系统调用)
    std::cout << "Contents of directory:\n";
    for (const auto& entry : fs::directory_iterator(dir_path)) {
        std::cout << entry.path() << std::endl;
    }

    // 删除文件和目录 (底层可能使用 unlink, rmdir 系统调用)
    fs::remove(new_file_path);
    fs::remove(dir_path);

    return 0;
}
path类

文件路径相关操作,如指定的路径是否存在等

directory_entry类

获取文件属性等,如指定文件是否是常规文件,还包括文件大小、文件最后修改时间

directory_iterator类

遍历目录,获取目录文件,不包括子目录

recursive_directory_iterator类

遍历目录,获取目录文件,包括子目录

filesystem_error类

继承自std::system_error

  1. path1, path2:返回存储在异常对象中的path;
  2. what: 返回解释性字符串;
  3. code: 返回错误码。
int test_filesystem_filesystem_error()
{
	// path1, path2, whtat
	// creates a unique filename that does not name a currently existing file
	const fs::path oldp{ std::tmpnam(nullptr) }, newp{ std::tmpnam(nullptr) };
 
	/* windows:
		what():  rename: 系统找不到指定的文件。: "C:\Users\f06190\AppData\Local\Temp\sf2w.0", "C:\Users\f06190\AppData\Local\Temp\sf2w.1"
		path1(): "C:\\Users\\f06190\\AppData\\Local\\Temp\\sf2w.0"
		path2(): "C:\\Users\\f06190\\AppData\\Local\\Temp\\sf2w.1" */
	/* linux
		what():  filesystem error: cannot rename: No such file or directory [/tmp/filezJrUkO] [/tmp/filey7tqKV]
		path1(): "/tmp/filezJrUkO"
		path2(): "/tmp/filey7tqKV" */
	try {
		fs::rename(oldp, newp); // throws since oldp does not exist
	} catch (fs::filesystem_error const& ex) {
		std::cout << "what():  " << ex.what() << '\n'
			<< "path1(): " << ex.path1() << '\n'
			<< "path2(): " << ex.path2() << '\n';
	}
 
	return 0;
}
file_status类
  1. .type: 获取或设置文件类型信息;
  2. .permissions: 获取或设置文件权限信息。
void demo_status(const fs::path& p, fs::file_status s)
{
	std::cout << p;
	switch (s.type()) {
	case fs::file_type::none:
		std::cout << " has `not-evaluated-yet` type";
		break;
	case fs::file_type::not_found:
		std::cout << " does not exist";
		break;
	case fs::file_type::regular:
		std::cout << " is a regular file";
		break;
	case fs::file_type::directory:
		std::cout << " is a directory";
		break;
	case fs::file_type::symlink:
		std::cout << " is a symlink";
		break;
	case fs::file_type::block:
		std::cout << " is a block device";
		break;
	case fs::file_type::character:
		std::cout << " is a character device";
		break;
	case fs::file_type::fifo:
		std::cout << " is a named IPC pipe";
		break;
	case fs::file_type::socket:
		std::cout << " is a named IPC socket";
		break;
	case fs::file_type::unknown:
		std::cout << " has `unknown` type";
		break;
	default:
		std::cout << " has `implementation-defined` type";
		break;
	}
	std::cout << '\n';
}
 
void demo_perms(fs::perms p)
{
	using fs::perms;
	auto show = 
	[=](char op, perms perm) {
		std::cout << (perms::none == (perm & p) ? '-' : op);
	};
 
	show('r', perms::owner_read);
	show('w', perms::owner_write);
	show('x', perms::owner_exec);
	show('r', perms::group_read);
	show('w', perms::group_write);
	show('x', perms::group_exec);
	show('r', perms::others_read);
	show('w', perms::others_write);
	show('x', perms::others_exec);
	std::cout << '\n';
}
 
int test_filesystem_file_status()
{
#ifdef _MSC_VER
	const std::string path{ "../../../testdata/list.txt" };
#else
	const std::string path{ "../../testdata/list.txt" };
#endif
 
	// 1. type
	// windows: "../../../testdata/list.txt" is a regular file
	// linux: "../../testdata/list.txt" is a regular file
	demo_status(path, fs::status(path));
 
	// 2. permissions
	demo_perms(fs::status(path).permissions()); // rwxrwxrwx
 
	return 0;
}
space_info结构体

获取有关文件系统上的空闲(free)和可用空间(available space)的信息

struct space_info {
    std::uintmax_t capacity; // 总大小,以字节为单位
    std::uintmax_t free; // 可用空间,以字节为单位
    std::uintmax_t available; // 非特权进程可用的可用空间(可能等于或小于可用空间)
};
float get_file_size(std::uintmax_t size, std::string& suffix)
{
	float s1 = size / 1024. / 1024 / 1024;
	float s2 = size / 1024. / 1024;
	float s3 = size / 1024.;
 
	if (s1 > 1) {
		suffix = " GB";
		return s1;
	}
	if (s2 > 1) {
		suffix = " MB";
		return s2;
	}
	if (s3 > 1) {
		suffix = " KB";
		return s3;
	}
	suffix = " Bytes";
	return size;
}
 
int test_filesystem_space_info()
{
	fs::space_info info = fs::space(fs::current_path());
	std::cout << "current path: " << fs::current_path() << "  ";
	// windows: current path: "E:\\GitCode\\Messy_Test\\prj\\x86_x64_vc12\\CppBaseTest"   size: 311.00 GB   size: 189.23 GB   size: 189.23 GB
	// linux: current path: "/home/spring/GitCode/Messy_Test/prj/linux_cmake_CppBaseTest"   size: 311.00 GB   size: 189.23 GB   size: 189.23 GB
	for (auto x : { info.capacity, info.free, info.available }) {
		std::string suffix;
		auto value = get_file_size(static_cast<std::intmax_t>(x), suffix);
		std::cout << " size: " << std::fixed << std::setprecision(2) << value << suffix << "  ";
	}
	std::cout << std::endl;
 
	return 0;
}
file_type枚举类

文件的类型

enum class file_type {
    none = /* unspecified */,
    not_found = /* unspecified */,
    regular = /* unspecified */,
    directory = /* unspecified */,
    symlink = /* unspecified */,
    block = /* unspecified */,
    character = /* unspecified */,
    fifo = /* unspecified */,
    socket = /* unspecified */,
    unknown = /* unspecified */,
    /* implementation-defined */
};
perms枚举类

文件访问权限

perm_options枚举类

控制函数std::filesystem::permissions()行为的可用选项

enum class perm_options {
    replace = /* unspecified */,
    add = /* unspecified */,
    remove = /* unspecified */,
    nofollow = /* unspecified */
};
非成员函数

(1).absolute:返回绝对路径;
(2).canonical, weakly_canonical: 将指定路径转换为规范绝对路径(canonical absolute path),指定的路径必须存在;
(3).relative, proximate: 返回相对路径;
(4).copy: 拷贝文件或目录,会生成新的文件或目录,from指定的文件或目录必须存在,to指定的文件或目录必须不存在;
(5).copy_file: 拷贝文件,会生成新的文件,from指定的文件必须存在,to指定的文件必须不存在;若发生错误,则返回false;
(6).copy_symlink:拷贝符号链接;
(7).create_directory, create_directories: 创建新目录,使用create_directory时要求父目录必须已存在,即create_directory不支持多级创建,而create_directories没有这些限制;
(8).create_hard_link:创建硬链接;
(9).create_symlink, create_directory_symlink: 创建符号链接;
(10).current_path: 获取当前工作目录;
(11).exists: 检查指定的文件或目录是否存在;
(12).equivalent: 检查两个路径是否是相同的;
(13).file_size: 获取文件大小;
(14).hard_link_count: 获取指定路径硬链接数;
(15).last_write_time: 获取或设置指定路径最后一次修改时间;
(16).permissions: 修改文件访问权限;
(17).read_symlink: 获取符号链接的target;
(18).remove, remove_all:删除文件或空目录;remove_all可递归地删除文件或目录及其所有内容并返回已删除的文件和目录的数量;
(19).rename: 重命名文件或目录;
(20).resize_file: 更改常规文件大小;
(21).space: 获取指定的路径的可用空间;
(22).status, symlink_status: 获取文件属性,如指定路径是否是常规文件、是否是目录等;
(23).temp_directory_path: 返回适合临时文件的目录。
std::error_code类是与平台相关的错误码。std::filesystem中的所有函数都有一个对应非抛出异常的对等接口。

int test_filesystem_non_member_functions()
{
	const fs::path p1 = "../funset.cpp";
#ifdef _MSC_VER
	const fs::path p2{ "../../../testdata/list.txt" }, p3{ "../../../testdata/list_copy.txt" }, p4{ "E:\\yulong.mp4" }, p5{ "../../../testdata/list_new.txt" };
#else
	const fs::path p2{ "../../testdata/list.txt" }, p3{ "../../testdata/list_copy.txt" }, p4{ "./build/CppBaseTest" }, p5{"../../testdata/list_new.txt"};
#endif
	// windows: current path is "E:\\GitCode\\Messy_Test\\prj\\x86_x64_vc12\\CppBaseTest"
	// linux: current path is "/home/spring/GitCode/Messy_Test/prj/linux_cmake_CppBaseTest"
	std::cout << "current path is " << fs::current_path() << '\n';
	// 1. absolute
	// windows: absolute path for "../funset.cpp" is "E:\\GitCode\\Messy_Test\\prj\\x86_x64_vc12\\funset.cpp"
	// linux: absolute path for "../funset.cpp" is "/home/spring/GitCode/Messy_Test/prj/linux_cmake_CppBaseTest/../funset.cpp"
	std::cout << "absolute path for " << p1 << " is " << fs::absolute(p1) << '\n';
 
	// 2. canonical, weakly_canonical
	/* windows:
		canonical path: "E:\\GitCode\\Messy_Test\\testdata\\list.txt"
		weakly canonical path: "E:\\GitCode\\Messy_Test\\testdata\\list.txt" */
	/* linux:
		canonical path: "/home/spring/GitCode/Messy_Test/testdata/list.txt"
		weakly canonical path: "/home/spring/GitCode/Messy_Test/testdata/list.txt" */
	std::cout << "canonical path: " << fs::canonical(p2) << "\n";
	std::cout << "weakly canonical path: " << fs::weakly_canonical(p2) << "\n";
 
	// 3. relative, proximate
	std::cout << "relative path: " << fs::relative("/a/b/c", "/a/b")
		<< ", proximat path: " << fs::proximate("/a/b/c", "/a/b") << "\n";	// relative path: "c", proximat path: "c"
 
	// 4. copy, exists, remove
	if (fs::exists(p3))
		fs::remove(p3);
	fs::copy(p2, p3);
 
	// 5. copy_file
	if (fs::exists(p3))
		fs::remove(p3);
	fs::copy_file(p2, p3);
 
	// 6. create_directory, create_directories
	fs::create_directory("./a");
	fs::create_directories("./b/c/d");
 
	// 7. equivalent
	if (fs::equivalent(".", fs::current_path()))
		std::cout << "they are equal" << "\n";
 
	// 8. file_size
	std::string suffix;
	auto value = get_file_size(static_cast<std::intmax_t>(fs::file_size(p4)), suffix);
	// windows: size: 1.35 GB; linux: size: 7.61 MB
	std::cout << "size: " << std::fixed << std::setprecision(2) << value << suffix << "\n";
 
	// 9. last_write_time
	// windows: last write time: 2023-08-19 22:42:56
	// linux: last write time: 2023-10-03 12:32:49
	std::cout << "last write time: " << to_time_t(last_write_time(p4)) << std::endl;
 
	// 10. permissions
	// windows: rwxrwxrwx r-xr-xr-x
	// linux:   rw-rw-r-- -w-r-----
	demo_perms(fs::status(p3).permissions());
#ifdef _MSC_VER
	fs::permissions(p3, fs::perms::none);
#else
	fs::permissions(p3, fs::perms::owner_write | fs::perms::group_read);
#endif
	demo_perms(fs::status(p3).permissions());
 
	// 11. rename
	if (fs::exists(p5))
		fs::remove(p5);
	fs::rename(p3, p5);
 
	// 12. resize_file
	// linux: size: 187.00 Bytes  size: 64.00 KB
	value = get_file_size(static_cast<std::intmax_t>(fs::file_size(p5)), suffix);
	std::cout << "size: " << std::fixed << std::setprecision(2) << value << suffix << "\n";
#ifdef __linux__
	fs::resize_file(p5, 64 * 1024); // resize to 64 KB, windows crash
	value = get_file_size(static_cast<std::intmax_t>(fs::file_size(p5)), suffix);
	std::cout << "size: " << std::fixed << std::setprecision(2) << value << suffix << "\n";
#endif
 
	// 13. temp_directory_path
	// windows: temp directory is: "C:\\Users\\f06190\\AppData\\Local\\Temp\\"
	// linux: temp directory is: "/tmp"
	std::cout << "temp directory is: " << fs::temp_directory_path() << "\n";
 
	// std::error_code
	std::error_code ec;
	fs::copy_file("xxx", "yyy", ec); // does not throw
	// windows: error code: 2,系统找不到指定的文件。
	// linux: error code: 2,No such file or directory
	std::cout << "error code: " << ec.value() << "," << ec.message() << "\n";
 
	try {
		fs::copy_file("xxx", "yyy");
	} catch (fs::filesystem_error const& ex) {
		std::cout << "what():  " << ex.what() << '\n'
			<< "path1(): " << ex.path1() << '\n'
			<< "path2(): " << ex.path2() << '\n'
			<< "code().value():    " << ex.code().value() << '\n'
			<< "code().message():  " << ex.code().message() << '\n'
			<< "code().category(): " << ex.code().category().name() << '\n';
	}
 
	return 0;
}
文件类型
  (1).is_block_file: 检查给定的文件或路径是否是块设备;
  (2).is_character_file: 检查给定的文件或路径是否是字符设备;
  (3).is_directory: 检查给定的文件或路径是否是目录;
  (4).is_empty: 检查给定的文件或路径是否是空文件或空目录;
  (5).is_fifo: 检查给定的文件或路径是否是命名管道;
  (6).is_other: 检查给定的文件或路径是否是其它文件;
  (7).is_regular_file: 检查给定的文件或路径是否是常规文件;
  (8).is_socket: 检查给定的文件或路径是否是套接字;
  (9).is_symlink: 检查给定的文件或路径是否是符号链接;
  (10).status_known: 检查给定的文件是否已知。
void demo_status2(const fs::path& p, fs::file_status s)
{
	std::cout << p;
	// alternative: switch(s.type()) { case fs::file_type::regular: ...}
	if (fs::is_regular_file(s))
		std::cout << " is a regular file\n";
	if (fs::is_directory(s))
		std::cout << " is a directory\n";
	if (fs::is_block_file(s))
		std::cout << " is a block device\n";
	if (fs::is_character_file(s))
		std::cout << " is a character device\n";
	if (fs::is_fifo(s))
		std::cout << " is a named IPC pipe\n";
	if (fs::is_socket(s))
		std::cout << " is a named IPC socket\n";
	if (fs::is_symlink(s))
		std::cout << " is a symlink\n";
	if (!fs::exists(s))
		std::cout << " does not exist\n";
	//if (fs::is_empty(p))
	//	std::cout << " is empty\n";
	if (fs::is_other(s)) // equivalent to exists(s) && !is_regular_file(s) && !is_directory(s) && !is_symlink(s)
		std::cout << " is other file\n";
	//if (fs::status_known(s)) // equivalent to s.type() != file_type::none
	//	std::cout << " is status known\n";
}
 
int test_filesystem_file_types()
{
	demo_status2("/dev/null", fs::status("/dev/null"));
	demo_status2("/dev/sda", fs::status("/dev/sda"));
	demo_status2(fs::current_path(), fs::status(fs::current_path()));
	demo_status2("/xxx/yyy", fs::status("/xxx/yyy"));
	demo_status2("/usr/bin/g++", fs::status("usr/bin/g++"));
	demo_status2("../../../testdata/list.txt", fs::status("../../../testdata/list.txt"));
	demo_status2("../../testdata/list.txt", fs::status("../../testdata/list.txt"));
	demo_status2("/mnt", fs::status("/mnt"));
 
	return 0;
}

std::shared_mutex

shared_mutex可以实现读写锁
适用场景:一个或多个读线程同时读取共享资源,且只有一个写线程来修改这个资源

#include "stdafx.h"
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <list>
#include <iostream>
#include <vector>
 
#define READ_THREAD_COUNT 8  
#define LOOP_COUNT 5000000  
 
typedef std::shared_lock<std::shared_mutex> ReadLock; // 读锁
typedef std::lock_guard<std::shared_mutex> WriteLock; // 写锁
typedef std::lock_guard<std::mutex> NormalLock;
 
class shared_mutex_counter {
public:
    shared_mutex_counter() = default;
 
    unsigned int get() const {
        ReadLock lock(mutex);
        return value;
    }
 
    void increment() {
        WriteLock lock(mutex);
        value++;
    }
 
private:
    mutable std::shared_mutex mutex;
    unsigned int value = 0;
};
 
class mutex_counter {
public:
    mutex_counter() = default;
 
    unsigned int get() const {
        NormalLock lock(mutex);
        return value;
    }
 
    void increment() {
        NormalLock lock(mutex);
        value++;
    }
 
private:
    mutable std::mutex mutex;
    unsigned int value = 0;
};
 
class timers
{
public:
    timers()
    {
        m_begin = std::chrono::high_resolution_clock::now();
    }
 
    ~timers()
    {
        m_end = std::chrono::high_resolution_clock::now();
        Consuming();
    }
 
    void Consuming()
    {
        std::cout << "Time-consuming:" << std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(m_end - m_begin).count() << std::endl;
    }
 
private:
    std::chrono::high_resolution_clock::time_point m_begin;
    std::chrono::high_resolution_clock::time_point m_end;
};
 
 
void test_shared_mutex()
{
    shared_mutex_counter counter;
    unsigned int temp;
 
    auto writer = [&counter]() {
        for (unsigned int i = 0; i < LOOP_COUNT; i++){
            counter.increment();
        }
    };
 
    auto reader = [&counter, &temp]() {
        for (unsigned int i = 0; i < LOOP_COUNT; i++) {
            temp = counter.get();
        }
    };
 
    std::cout << "----- shared mutex test ------" << std::endl;
    std::list<std::shared_ptr<std::thread>> threadlist;
    {
        timers timer;
 
        for (int i = 0; i < READ_THREAD_COUNT; i++)
        {
            threadlist.push_back(std::make_shared<std::thread>(reader));
        }
        std::shared_ptr<std::thread> pw = std::make_shared<std::thread>(writer);
 
        for (auto &it : threadlist)
        {
            it->join();
        }
        pw->join();
    }
    std::cout <<"count:"<< counter.get() << ", temp:" << temp << std::endl;
}
 
void test_mutex()
{
    mutex_counter counter;
    unsigned int temp;
 
    auto writer = [&counter]() {
        for (unsigned int i = 0; i < LOOP_COUNT; i++) {
            counter.increment();
        }
    };
 
    auto reader = [&counter, &temp]() {
        for (unsigned int i = 0; i < LOOP_COUNT; i++) {
            temp = counter.get();
        }
    };
 
    std::cout << "----- mutex test ------" << std::endl;
    std::list<std::shared_ptr<std::thread>> threadlist;
    {
        timers timer;
 
        for (int i = 0; i < READ_THREAD_COUNT; i++)
        {
            threadlist.push_back(std::make_shared<std::thread>(reader));
        }
 
        std::shared_ptr<std::thread> pw = std::make_shared<std::thread>(writer);
 
        for (auto &it : threadlist)
        {
            it->join();
        }
        pw->join();
    }
    std::cout << "count:" << counter.get() << ", temp:" << temp << std::endl;
}
 
int main()
{
    test_shared_mutex();
    test_mutex();
    return 0;
}

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值