7.4小学期基础语法记录:bitwidth、智能指针

🚲 std::bit_width 介绍

std::bit_width 是 C++20 引入的一个非常实用的函数,定义在头文件 <bit> 中,用于计算一个正整数的“有效位数”,也就是最左边的 1 所在的位置的下标加 1。这个函数在处理二进制算法、优化掩码操作、设计高效数据结构(如动态数组、位图等)时非常有用,尤其在分布式系统、图算法和内存优化结构设计中发挥着举足轻重的作用。


✅ 函数定义

#include <bit>

template< class T >
constexpr int bit_width( T x ) noexcept;
  • T 必须是无符号整数类型(如 unsigned int, uint64_t 等)。
  • 返回的是 x 的二进制表示中需要的最小位数来表示它。
  • 该函数是 constexpr 的,可以用于编译期常量推导,适合模板元编程场景。

📌 语义解释

std::bit_width(x) 的含义是:

返回最小的正整数 n,使得 x < (1 << n)

换句话说,它返回:

x == 0      -> 0
x > 0       -> floor(log2(x)) + 1

这个函数本质上等价于“有效位宽”,即 x 的最高有效位下标加 1,对于优化二进制存储空间与计算逻辑有重要意义。


✅ 示例说明

#include <iostream>
#include <bit>

int main() {
    std::cout << std::bit_width(0u) << "\n";       // 输出:0
    std::cout << std::bit_width(1u) << "\n";       // 输出:1
    std::cout << std::bit_width(2u) << "\n";       // 输出:2
    std::cout << std::bit_width(3u) << "\n";       // 输出:2
    std::cout << std::bit_width(4u) << "\n";       // 输出:3
    std::cout << std::bit_width(255u) << "\n";     // 输出:8
    std::cout << std::bit_width(256u) << "\n";     // 输出:9
    std::cout << std::bit_width(1023u) << "\n";    // 输出:10
    std::cout << std::bit_width(1024u) << "\n";    // 输出:11
}

这种语义在分配表空间、查找表优化、浮点编码压缩等方面都可以作为基础指标使用。


📊 与其他相关函数对比

函数名 功能简述 返回类型
std::bit_width(x) 返回 floor(log2(x)) + 1,即有效位宽 int
std::countl_zero(x) 统计高位前导 0 的个数 int
std::countl_one(x) 统计高位前导 1 的个数 int
std::countr_zero(x) 统计低位尾部 0 的个数 int
std::countr_one(x) 统计低位尾部 1 的个数 int
std::popcount(x) 统计 1 的个数(population count) int
std::has_single_bit(x) 判断是否是 2 的幂 bool

🔧 底层实现原理(简化版)

在底层,std::bit_width 通常使用如下结构快速实现(以下是伪代码思路):

int my_bit_width(unsigned int x) {
    if (x == 0) return 0;
    return 32 - __builtin_clz(x); // GCC/Clang 内建函数
}
  • __builtin_clz(x):返回前导 0 的个数
  • 所以 bit_width(x) = 32 - clz(x),也就是最高位的索引 + 1
  • 对于 uint64_t 会使用 __builtin_clzll

编译器往往会将其优化为单条硬件指令,如 LZCNT, BSR, 或在 ARM 上使用 CLZ


🚀 应用场景举例

1. 动态跳表(Skip List)高度控制

使用 std::bit_width(n) 来设定最大高度,使得跳表层数随节点数量自适应变化,减少不必要的多层索引维护:

int maxHeight = std::bit_width(currentNodeCount);

2. 哈希表桶定位或动态扩容

通过 bit_width 快速判断插入数据所需最小桶位数:

size_t get_bucket_index(size_t bytes) {
    return std::bit_width(bytes - 1);
}

3. 内存池桶划分(如 jemalloc)

内存池常使用 2 的幂桶分配,快速定位分配类别:

void* alloc(size_t size) {
    int bucket = std::bit_width(size - 1);
    return pool[bucket].alloc();
}

4. 位图索引压缩(数据库)

列存数据库中对枚举类型字段可压缩至最小位宽:

int bits_per_entry = std::bit_width(numDistinctValues);

5. 稀疏图邻接表优化

稀疏图中,使用 bit_width 精确分配索引位宽可减少无谓内存开销:

int bits = std::bit_width(degree[v]);

6. 动态 Bitset 构造与位图容器压缩

用于估算需要多少块来存储 n 位:

int blockCount = (n + 63) / 64;
int bitsPerIndex = std::bit_width(blockCount);

7. Hash ID 编码(如 Twitter Snowflake)

合理分配 Snowflake 各字段位宽:

int machineBits = std::bit_width(machineCount);

8. Perfect Hash 编码空间控制

构造 minimal perfect hash 时计算最紧凑位数:

int hashBits = std::bit_width(tableSize);

9. 高维向量压缩编码

将多维值嵌入单个 64bit 时,需对每维宽度用 bit_width 分配,减少浪费。


🧠 举一反三

  1. bit_width(x) - 1 就是 log2(x) 的整数部分(前提是 x > 0

  2. 若不希望使用浮点运算实现 log2(x),可采用 bit_width(x) - 1 形式代替

  3. 判断一个数是否是 2 的幂,可使用:

    std::has_single_bit(x)
    
  4. bit_width 可用于实现快速的 binary lifting 优化跳跃长度、跳跃倍增搜索等高效算法。

  5. Bit Trie 等压缩前缀树结构中,每层位宽也可使用 bit_width 动态估算。


⚠️ 易错点总结

  • x == 0 返回 0 是特例,要显式处理,不能直接参与位运算

  • bit_width(2^n) == n + 1,不是 n

    • 示例:bit_width(8) = 4,因为 8 == 1000b
  • 输入有符号整数时需注意类型转换:

    std::bit_width(-1); // UB,需手动转 unsigned 类型
    
  • 不能直接用于浮点数,需先转换为整数或 reinterpret_cast

  • 对于复合模板结构(如泛型容器),需手动添加 std::make_unsigned_t<T> 进行适配

  • bit_width 返回的是 位数 而不是最高位位置,不要与 __lgfloor(log2()) 混淆


总结

特点 描述
功能 计算最左边 1 所在的位置 + 1,用于估算所需位宽
性能 极高,通常编译为单条硬件指令(如 CLZ, LZCNT)
用途 二进制优化、数据结构设计、图结构压缩、分布式编码、多维数据映射等
C++版本要求 C++20 起提供,定义在 <bit>
常配合使用函数 std::countl_zero, std::has_single_bit, std::popcount
编译期支持 支持 constexpr,适用于模板元编程

C++ 智能指针详细笔记

1. 概述

  • 智能指针(Smart Pointer)是标准库(<memory>)提供的模板类,用于管理动态对象的生命周期,自动释放资源,减少内存泄漏风险。
  • 主要类型:
    • std::unique_ptr
    • std::shared_ptr
    • std::weak_ptr
  • (历史:std::auto_ptr 已弃用,C++11 后不再使用)

2. std::unique_ptr

2.1 定义与特点

  • 独占拥有权,不能被拷贝,只能移动。
  • 无额外引用计数,开销最小。

2.2 常用方法

  • 构造

    std::unique_ptr<T> p1;                    // 空指针
    std::unique_ptr<T> p2(new T(args));       // 直接构造
    auto p3 = std::make_unique<T>(args...);   // 推荐:异常安全、表达简洁
    
  • 访问

    • p.get():返回原始裸指针,不改变所有权。
    • *pp->成员:解引用操作。
  • 所有权转移

    • auto p2 = std::move(p1);:移动构造或赋值,原指针置空。
  • 资源释放

    • p.reset(new T);:释放旧对象并接管新对象。
    • p.reset();:释放管理的对象,置空。
    • p.release();:释放管理权,返回裸指针,unique_ptr 置空,仅当手动管理资源时使用。

2.3 注意事项

  • 不要显式拷贝:std::unique_ptr<T> p2 = p1; 编译错误。
  • 使用 make_unique 优于 new,防止异常导致的内存泄漏。
  • release() 后必须手动 delete,否则泄漏。
  • 适用于单一拥有者场景,如工厂函数返回值。

3. std::shared_ptr

3.1 定义与特点

  • 共享指针,可拷贝、赋值,使用引用计数管理资源。
  • 当最后一个 shared_ptr 销毁或重置时,释放资源。
  • 引用计数及弱计数开销,线程安全(引用计数的原子操作)。

3.2 常用方法

  • 构造

    std::shared_ptr<T> p1;                    // 空指针
    std::shared_ptr<T> p2(new T(args));       // 直接构造
    auto p3 = std::make_shared<T>(args...);   // 推荐:性能最佳、内存连续
    std::shared_ptr<T> p4(p3);                // 引用计数 +1
    
  • 访问

    • p.get():返回原始指针。
    • *pp->成员:解引用。
  • 引用计数查询

    • p.use_count():当前 shared_ptr 数量。
    • p.unique():是否唯一拥有(use_count() == 1)。
  • 重置与交换

    • p.reset(new T);:重置并接管新对象,原对象引用计数减1。
    • p.reset();:释放管理的对象。
    • p.swap(q);:交换两个 shared_ptr 所持指针。
  • 别名构造

    std::shared_ptr<Base> basePtr = std::make_shared<Derived>();
    std::shared_ptr<Derived> alias(basePtr, static_cast<Derived*>(basePtr.get()));
    
    • 用于管理与子对象或自定义释放策略。

3.3 注意事项

  • 循环引用:A 持有 shared_ptr<B>B 又持有 shared_ptr<A> 会导致内存泄漏。
  • 使用 weak_ptr 打破循环引用。
  • make_shared 在一次分配中分配对象与引用计数,避免多次内存分配。
  • 避免 new + shared_ptr 直接构造,推荐 make_shared
  • 多线程场景下,shared_ptr 的引用计数原子操作有性能开销。

4. std::weak_ptr

4.1 定义与特点

  • 不拥有资源,不增加引用计数。
  • 用于观察由 shared_ptr 管理的对象,解决循环引用问题。

4.2 常用方法

  • 构造

    std::weak_ptr<T> wp;                   // 空弱指针
    std::weak_ptr<T> wp2(sharedPtr);      // 转换构造,不增加 shared 引用计数
    
  • 过期检测与获取

    • wp.expired():检查托管对象是否已删除。
    • std::shared_ptr<T> sp = wp.lock();:尝试获取 shared_ptr,若对象有效,计数 +1,否则返回空 shared_ptr

4.3 注意事项

  • 调用 lock 后需检查返回的 shared_ptr 是否为空。
  • 不可直接 *wpwp->
  • 适用于缓存、观察者模式等不影响对象生命周期的场景。

5. 其他注意事项与前瞻

  • 自定义删除器

    auto p = std::shared_ptr<FILE>(fopen("...","r"), [](FILE* fp){ if(fp) fclose(fp); });
    
    • 可管理非 new 分配的资源。
  • 性能考量

    • unique_ptr 最轻量;
    • shared_ptr 需引用计数,且线程安全,成本较高;
    • weak_ptr 无计数成本,但通过 lock 增加一次
  • 内存对齐与池分配:未来 C++ 标准可能引入更高效的内存分配模型(如内存池结合智能指针)。

  • 自定义指针类型:可以继承或包装,增加日志、调试功能。


总结

  • unique_ptr:独占所有权,轻量级,首选单一拥有场景。
  • shared_ptr:共享所有权,引用计数,支持自定义删除器,但需防止循环引用。
  • weak_ptr:观察者,解决 shared_ptr 循环,引入后访问需 lock()
posted @ 2025-07-04 20:04  十八Eigh18n  阅读(128)  评论(0)    收藏  举报