C++20新特性_原子智能指针,std::source_location和位操作函数

文章目录

  • [第二章 C++20标准库特性](#第二章 C++20标准库特性)
    • [2.5 原子智能指针](#2.5 原子智能指针)
    • [2.6 std::source_location](#2.6 std::source_location)
      • [2.6.1 source_location 语法格式](#2.6.1 source_location 语法格式)
      • [2.6.2 举例](#2.6.2 举例)
    • [2.7 位操作函数](#2.7 位操作函数)

本文记录C++20新特性之原子智能指针、std::source_location和位操作函数。

第二章 C++20标准库特性

2.5 原子智能指针

C++11引入了智能指针,shared_ptr和unique_ptr是内存管理的利器。但是,shared_ptr不是线程安全的,虽然,std::shared_ptr 的引用计数控制块是线程安全的,但修改它指向的对象并不是线程安全的。所以,在多线程中读写同一个全局的 std::shared_ptr对象,面临数据竞争风险。

C++20之前的解决方法是,在多线程环境下,对共享变量加锁,以确保线程安全。

C++20 的解决方案: 直接特化 std::atomic 模板,允许 std::atomic<std::shared_ptr>。这使得智能指针的原子操作变得类型安全且自然。

2.5.1原子智能指针语法格式和常用方法

语法格式:原子智能指针使用起来像使用std::atomic 一样,只是模板参数是智能指针。

使用方式如下:

cpp 复制代码
    struct Data 
    { 
        int value;
    };

    void test()
    {
        // 定义一个原子的 shared_ptr
        std::atomic<std::shared_ptr<Data>> atomic_ptr;
        // 定义一个原子的 weak_ptr
        std::atomic<std::weak_ptr<Data>> atomic_weak;
	}

常用方法:

  • std::atomic<std::shared_ptr> 提供了标准原子类型的一系列方法,确保了对指针本身操作的原子性。
  • store(val): 原子地替换存储的指针。
  • load(): 原子地返回当前指针的一个副本(增加引用计数)。
  • exchange(val): 原子地替换指针并返回旧值。
  • compare_exchange_strong/weak(...): CAS(Compare-And-Swap)操作,用于无锁编程。
  • wait/notify: 支持 C++20 的原子等待机制(类似于条件变量,但更轻量)。
cpp 复制代码
    void test()
    {
        std::atomic<std::shared_ptr<int>> ptr = make_shared<int>(33);

        // 1 原子写入
		auto new_ptr = std::make_shared<int>(42);
        ptr.store(new_ptr, std::memory_order_release); // 都指向了ptr 
        cout << "引用计数 " << new_ptr.use_count() << endl;
        // 引用计数 2
        
		// 输出 ptr 的值
		cout << "ptr = " << *ptr.load() << endl;
        // 42 
		cout << " new_ptr = " << *new_ptr << endl;
        // 42 
		cout << "ptr 地址 = " << ptr.load().get() << endl;
        // ptr 地址 =     0000018FDC3387A0
		cout << "new_ptr 地址 = " << new_ptr.get() << endl;
        // new_ptr 地址 = 0000018FDC3387A0
        cout << "引用计数 " << ptr.load().use_count() << endl;
        // 引用计数 3
  
        // 2 原子读取,load 返回副本
        auto loaded_ptr = ptr.load(std::memory_order_acquire);
        std::cout << "Value: " << *loaded_ptr << std::endl;
		// Value: 42
    }

2.5.2 使用总结

在多线程环境下,可以使用 原子智能指针来确保线程安全,这虽然比裸指针的原子操作慢,但比使用mutex 保护 shared_ptr快,也可以避免死锁风险。

2.6 std::source_location

在C++20之前,为了记录当前代码所在文件名,行号,函数名,使用如下方式:

cpp 复制代码
// 必须使用宏,否则 __LINE__ 会是 log 函数内部的行号,而不是调用处的行号
#define LOG(msg) log_message(msg, __FILE__, __LINE__, __func__)

void log_message(const char* msg, const char* file, int line, const char* func) {
    // ...
}

这种方式缺点:

1 宏是文本替换:容易导致命名冲突,且调试困难。

2 参数传递困难:你无法轻松地将"调用者的位置"作为一个默认参数传递给函数,因为 LINE 在参数默认值处展开时,行为可能不符合预期(或者需要非常复杂的宏技巧)。

3 类型不安全:FILE 只是个 const char*,没有封装。

C++20 引入了 std::source_location,提供了一种标准、类型安全且零开销的方式来获取源代码信息。

2.6.1 source_location 语法格式

std::source_location 是一个类,包含在 <source_location>头文件中,编译器会对其进行特殊处理。当它作为函数的默认参数时,编译器会自动填入调用该函数时的源代码位置。

source_location 提供了一个静态函数 current(),我们可以使用这个函数获取代码的文件名,行号等信息。

常用方法:

  • static current(): 获取当前代码位置的 source_location 对象。
  • file_name(): 返回文件名 (const char*)。
  • function_name(): 返回函数名 (const char*)。
  • line(): 返回行号 (std::uint_least32_t)。
  • column(): 返回列号 (std::uint_least32_t)。
    使用示例如下:
cpp 复制代码
    void log(const char* message, const std::source_location& location = std::source_location::current())
    {
        std::cout << "File: " << location.file_name() << "\n"
            << "Line: " << location.line() << "\n"
            << "Func: " << location.function_name() << "\n"
            << "Message: " << message << "\n";
    }

    void test()
    {
        log("This is a log message.");
        /*
            File: E:\cpp_learning\newFeature\newFeature\newFeature.cpp
            Line: 2579
            Func: void __cdecl sp60::test(void)
            Message: This is a log message.
		*/
    }

2.6.2 举例

使用C++20的source_location实现一个不依赖宏的日志函数。

cpp 复制代码
    enum class LogLevel
    {
        INFO,
        ERROR
    };

    class Logger
    {
    public:
        // 注意:location 必须是默认参数
        static void log(LogLevel level, std::string_view message,
            const std::source_location& loc = std::source_location::current())
        {
            std::cout << std::format("[{}] {}:{}:{} | {}\n",
                (level == LogLevel::INFO ? "INFO" : "ERROR"),
                loc.file_name(),
                loc.line(),
                loc.function_name(),
                message);
        }
    };

    void complex_calculation()
    {
        Logger::log(LogLevel::INFO, "Calculation started");
        //[INFO] newFeature.cpp:2592:  complex_calculation(void) | Calculation started
        Logger::log(LogLevel::ERROR, "Calculation failed");
        // [ERROR] newFeature.cpp:2594: complex_calculation(void) | Calculation failed
    }

    void test()
    {
        complex_calculation();
    }

2.7 位操作函数

C++20 引入了 头文件,提供了一组标准、跨平台、高性能且类型安全的位操作函数,给位操作提供了便利。

下面介绍bit库中常用的方法。

计数类 (Counting)

  • std::popcount(x): 计算二进制中 1 的个数 (Population Count)。
  • std::countl_zero(x): 从左(最高位)开始计算连续 0 的个数 (Count Leading Zeros)。
  • std::countl_one(x): 从左开始计算连续 1 的个数。
  • std::countr_zero(x): 从右(最低位)开始计算连续 0 的个数 (Count Trailing Zeros)。
  • std::countr_one(x): 从右开始计算连续 1 的个数。
cpp 复制代码
    int test() 
    {
        uint8_t x = 0b00101100; // 44

        std::cout << std::format("Num: {:08b}\n", x);
        std::cout << "计算1的数量为:: " << std::popcount(x) << "\n";     
        // 3
        std::cout << "从最高位计算,连续0的个数: " << std::countl_zero(x) << "\n";   
        // 2
        std::cout << "从右开始计算连续1的个数: " << std::countr_zero(x) << "\n";  
        // 2

        return 0;
    }

幂运算与对数类 (Powers & Logarithms)

  • std::has_single_bit(x): 检查 x 是否是 2 的幂(即二进制中是否只有一个 1)。
  • std::bit_ceil(x): 返回不小于 x 的最小的 2 的幂(向上取整到 2 的幂)。
  • std::bit_floor(x): 返回不大于 x 的最大的 2 的幂(向下取整到 2 的幂)。
  • std::bit_width(x): 返回表示 x 所需的最小二进制位数。
cpp 复制代码
    void test()
    {
        uint32_t val = 100;
        bool is_power_2 = std::has_single_bit(val); // 2的幂
        // false
        auto next_pow2 = std::bit_ceil(val);     // 返回不小于x 最小的2的幂    
        // 128
        auto prev_pow2 = std::bit_floor(val);     // 返回不大于x 最大的2的幂      
        // 64
		auto width = std::bit_width(val);   // 表示x的二进制表示所需的位数       
        // 7 (因为 100 是 1100100)
    }

字节序转换 (Endian)

std::endian: 一个枚举类,用于在编译期检测系统字节序。

cpp 复制代码
    void test()
    {
        if constexpr (std::endian::native == std::endian::little) 
        {
            std::cout << "Little Endian System\n";
        }
        else {
            std::cout << "Big Endian System\n";
        }
        // Little Endian System
    }

类型转换 (Bit Cast) : 使用std::bit_cast(From),将From类型的二进制位原封不动解释为To类型。

cpp 复制代码
float f = 3.14f;
// 安全地将 float 的位模式转换为 uint32_t
uint32_t i = std::bit_cast<uint32_t>(f); 
相关推荐
Mr_WangAndy3 小时前
C++20新特性_[[likely]] , [[unlikely]]属性和特性测试宏
c++20·likely·c++40周年·unlikely·特性测试宏
Mr_WangAndy3 小时前
C++20新特性_std::format和span
c++20·format·c++20新特性·span·c++40周年
ULTRA??4 小时前
C++20模块( import 核心用法)
c++·c++20
Mr_WangAndy19 小时前
C++20新特性_概念 (Concepts)
c++20·c++20新特性·c++40周年·c++20概念
Mr_WangAndy19 小时前
C++20新特性_范围 (Ranges)
c++20·c++20新特性·c++40周年·范围ranges·视图适配器·视图view
Mr_WangAndy1 天前
C++20新特性_[[no_unique_address]]属性
c++20·c++20新特性·c++40周年
Mr_WangAndy1 天前
C++20新特性_模块(Modules)
c++20·c++40周年·c++20新特性模块
Mr_WangAndy1 天前
C++20新特性_范围 `for` 循环的初始化语句
c++20·c++40周年·范围for初始化
Mr_WangAndy1 天前
C++20新特性_三路比较运算符 (<=>)
c++20·c++40周年·三路比较运算符