C++17 pmr

#include <iostream>
#include <cstdlib>
#include <iostream>
#include <memory_resource>
#include <vector>

int main() {
    char buffer[32] = {};
    std::fill_n(std::begin(buffer), std::size(buffer)-1, '_');
    std::cout << buffer << '\n';

    std::pmr::monotonic_buffer_resource pool{
        std::data(buffer), std::size(buffer)
    };

    std::pmr::vector<char> vec{&pool}; 
    
    for (char ch='a'; ch <= 'z'; ++ch)
        vec.push_back(ch);

    std::cout << buffer << '\n';
}

       pmr大概意思是“多态的内存资源”。C++03时代的STL容器使用的是被称为分配器allocator的内存管理模块,allocator是无状态(stateless)的。你会发现allocator的定义里没有成员变量,全是成员函数和一些typedef。自定义的allocator,也只能通过全局变量获取自己需要的外部信息(回到用全局变量做消息沟通的时代)。allocator是STL容器模板签名的一部分,不同的allocator模板实参具体化成一个新的容器类。例如:std::vector《int》  v1和 std::vector《int,my_allocator》 v2是两个完全无关的类型,他们之间不能之间做对象的赋值。也就是v1 = v2; 会引起编译错误。
        pmr这个新的名字空间里,引入了另一种不同思路的分配器,当然也要配合全新的一套容器模板类。这个新的分配器叫做pmr::polymorphic_allocator; 这个分配器是stl分配器兼容的,唯一区别是,它是个有状态的(statefull),简单理解就是这个类具有一个数据成员memory_resource*。这个类memory_resource就是一个接口类,定义了一些分配内存,回收内容的虚函数。简单的说,把内存请求,内存释放的功能分离出来到memory_resource的子类实现了。
        既然分配器做了重大设计,容器的设计也要改,他们应允许在构造自己时,传入一个分配器或者直接传入一个memory_resource的指针。
        下面,介绍例子代码
 

char buffer[32] = {};
std::fill_n(std::begin(buffer), std::size(buffer)-1, '_');
std::cout << buffer << '\n';

就是开辟了一块位于栈上的32字节空间,用横杠填入每个字符,除了最后一个保留'\0'
 

std::pmr::monotonic_buffer_resource pool{
   std::data(buffer), std::size(buffer)
};

上面这段话的意思是,创建了一个“单增内存资源”的实例对象,名字是pool。pool背后实际有两个后盾,第一个后盾很明显,就是刚才创建的那个char buffer[32]数组。第二个后盾不是很明显,叫做upstream。这个upstream就是另一个memory_resource对象,当char buffer[32]不够用时,对内存的请求就会转向upstream。是的,32字节的数组,很可能不够用。

std::pmr::vector<char> vec{&pool}; 

很明显,这是个新的vector的实现,它在pmr名字空间呢。使用传入的指针。我们知道,只有指针或者引用,才能实现多态,所以传入一个指针一点也不奇怪。monotonic_buffer_resource类继承自memory_resource。

for (char ch='a'; ch <= 'z'; ++ch)
     vec.push_back(ch);

std::cout << buffer << '\n';

开始向vector中push_back传入a-z, 再次观察buffer,cout的输出为:

_______________________________
aababcdabcdefghabcdefghijklmnop

第一行是一堆横杠,显然是开始fill_n调用的结果。
第二行,显示规律为:a(1)  ab(2)  abcd(4)  abcdefgh(8)  abcdefghijklmnop(16)
这不就是vector的指数级分配规律吗?当vector扩容时,按2倍空间扩容。
但是,为什么monotonic_buffer_resource不从头分配呢?这就是与它的名字monotonic有关了。
它是一个只吃不拉的貔貅,只知道拿新的,不知道还旧的。还有这种应用场合吗?当然非常广泛了,如果你不知道,就要记住了。比如:我不停的分配10000块小块内存,做处理。最后有两个方法归还:方法1,调用10000次归还内存的方法。方法2:如果这10000块内存来自一个完整的大块,其实我只需归还这一大块一次就可以了。

        另一个问题,那个q-z的数据哪里去了? 这就涉及到upstream,由它分配一块内存了。我们可以自定义一个memory_resource,只是用来观察。

#include <iostream>
#include <cstdlib>
#include <iostream>
#include <memory_resource>
#include <vector>

class debug_resource : public std::pmr::memory_resource {
public:
    explicit debug_resource( std::pmr::memory_resource* up = std::pmr::get_default_resource() )
        : _upstream{ up } 
    { }

    void* do_allocate(size_t bytes, size_t alignment) override {
        std::cout << " do_allocate(): " << bytes << '\n';
        return _upstream->allocate(bytes, alignment);
    }
    void do_deallocate(void* ptr, size_t bytes, size_t alignment) override {
        std::cout << " do_deallocate(): " << bytes << '\n';
        _upstream->deallocate(ptr, bytes, alignment);
    }
    bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
        return this == &other;
    }

private:
    std::pmr::memory_resource* _upstream;
};

int main() {
    
    char buffer[32] = {};
    std::fill_n(std::begin(buffer), std::size(buffer)-1, '_');
    std::cout << buffer << '\n';

    debug_resource dr;
    
    std::pmr::monotonic_buffer_resource pool{
        std::data(buffer), std::size(buffer),
        &dr
    };

    std::pmr::vector<char> vec{&pool}; 
    
    for (char ch='a'; ch <= 'z'; ++ch)
        vec.push_back(ch);

    std::cout << buffer << '\n';
}

我们发现,当pmr::vector需要64字节内存时,monotonic_buffer_resource无法满足需求,继而转向了dr请求64字节,但是dr也没法满足(它只是打印信息),dr把请求转发给自己的上游upstream,实际就是系统默认的一个std::pmr::get_default_resource()。 估计是执行new,delete.

        现在话题又回到monotonic_buffer_resource和vector配合的动机上,我们想利用栈空间存储
a-z,不想用有点低效的heap空间。如何修改一下程序满足这个要求呢?用vector的reserve() 。

int main() {
    
    char buffer[32] = {};
    std::fill_n(std::begin(buffer), std::size(buffer)-1, '_');
    std::cout << buffer << '\n';

    debug_resource dr;
    
    std::pmr::monotonic_buffer_resource pool{
        std::data(buffer), std::size(buffer),
        &dr
    };

    std::pmr::vector<char> vec{&pool}; 
    vec.reserve(26);   //  预先分配足够空间,避免二次分配
    
    for (char ch='a'; ch <= 'z'; ++ch)
        vec.push_back(ch);

    std::cout << buffer << '\n';
}

输出:

_______________________________
abcdefghijklmnopqrstuvwxyz_____

这完全符合预期!

参考:https://blog.feabhas.com/2019/03/thanks-for-the-memory-allocator/

The C++ language has a long history, dating back to the 1980s. Recently it has undergone a renaissance, with major new features being intro duced in 2011 and 2014. At press time, the C++17 standard is just around the corner. C++11 practically doubled the size of the standard library, adding such headers as , , and . C++17 doubles the library again, with additions such as , , and . A programmer who’s been spending time writing code instead of watching the standardization process might fairly feel that the standard library has gotten away fromhim--that there’s so many new things in the library that he'll never be able to master the whole thing, or even to sort the wheat fromthe chaff. After all, who wants to spend a month reading technical documentation on std::locale and std::ratio , just to find out that they aren't useful in your daily work? In this book, I'll teach you the most important features of the C++17 standard library. In the interest of brevity, I omit some parts, such as the aforementioned ; but we'll cover the entire modern STL (every standard container and every standard algorithm), plus such imp ortant topics as smart pointers, randomnumbers, regular expressions, and the new-in-C++17 library. I'll teach by example. You'll learn to build your own iterator type; your own memory allocator using std::pmr::memory_resource ; your own thread pool using std::future . I'll teach concepts beyond what you'd find in a reference manual. You'll learn the difference between monomorphic, polymorphic, and generic algorithms (Chapter 1 , Classical Polymorphism and Generic Programming ); what it means for std::string or std::any to be termed a "vocabulary type"(Chapter 5 , Vocabulary Types ); and what we might expect fromfuture C++ standards in 2020 and beyond. I assume that you are already reasonably familiar with the core language of C++11; for example, that you already understand how to write class and function templates, the difference between lvalue and rvalue references, and so on.
<think>好的,我现在需要回答用户关于C++17头文件的问题。用户之前已经询问过std::iterator被弃用的事情,现在转向头文件,可能想了解C++17新增或修改了哪些头文件,或者相关的变化。 首先,我需要回忆C++17标准中涉及头文件的部分。可能包括新增的头文件,比如<filesystem>,这是比较重要的新增库。还有<memory_resource>,这是针对多态内存资源的。此外,可能有某些现有头文件的更新,比如<optional>, <variant>, <any>,这些可能在C++17中引入。不过需要确认这些头文件是否确实是C++17新增的,比如<optional>确实是C++17的一部分。 然后,需要整理这些头文件的作用和主要类/函数。比如<filesystem>提供了文件系统的操作,包括路径、目录遍历等。<memory_resource>与内存池和分配器相关。还有可能其他头文件如<string_view>,虽然我好像记得<string_view>是C++17引入的,对应的头文件是<string_view>吗?是的,没错。 另外,C++17中可能有一些头文件的废弃或修改。比如之前提到的<ccomplex>, <ciso646>, <cstdalign>, <cstdbool>, <ctgmath>这些在C++17中被弃用,因为它们对应的C标准库部分在C++中用处不大或者有更好的替代。 还需要注意,有些头文件可能被合并或调整,比如<experimental>中的一些特性被正式纳入标准库,比如<experimental/filesystem>变成<filesystem>。这也是需要提到的点。 用户可能还关心如何正确使用这些新头文件,是否需要特定的编译器支持。比如,使用<filesystem>可能需要链接特定的库,比如在GCC中需要-lstdc++fs,Clang中可能需要-lc++fs,不过这个可能随着编译器版本不同而变化,需要指出可能需要查阅编译器的文档。 另外,要提到C++17中一些现有头文件的增强,比如<algorithm>中的新算法,如std::clamp, std::sample等,但用户的问题是头文件,所以可能重点在新增头文件而非现有头文件的新内容。不过可以简要提及这些内容,但需要明确区分。 可能的误区:用户可能混淆了C++17C++20的内容,比如<format>头文件是C++20的,所以需要确认哪些确实是C++17的。 总结需要分点,可能的结构是: 1. 新增头文件列表,每个头文件的作用和主要内容。 2. 被弃用或移除的头文件。 3. 现有头文件的重要更新(如果有)。 4. 使用这些头文件的注意事项,比如编译器支持、链接库等。 需要确保信息准确,比如确认每个头文件是否确实属于C++17。比如,<optional>, <variant>, <any>, <string_view>, <filesystem>, <memory_resource>都是C++17的新增头文件。 可能需要检查参考资料或权威来源,比如cppreference.com上的C++17页面,确认各个头文件的归属。 例如: - <optional>:提供std::optional - <variant>:提供std::variant和std::visit - <any>:提供std::any - <string_view>:提供std::string_view - <filesystem>:文件系统库 - <memory_resource>:内存池资源管理 - <charconv>:字符转换函数,如to_chars, from_chars 此外,C++17还引入了其他头文件,如<execution>用于并行算法,但该头文件属于标准算法的执行策略,属于现有头文件<algorithm>的扩展,但可能本身是新的头文件?或者执行策略是在<algorithm>中定义的?需要确认。根据记忆,执行策略相关的定义在<execution>头文件中,属于C++17的新增内容。 所以需要将<execution>列为新增头文件。 然后是被弃用的头文件,如前面提到的那些C兼容的头文件,如<ccomplex>等,这些在C++17中被标记为废弃。 在撰写回答时,需要结构清晰,分点说明,每个头文件简要介绍其用途和主要内容,并给出示例代码(如果需要)。同时,注意用户可能需要知道如何正确包含这些头文件,以及兼容性问题。 例如,对于<filesystem>,可能需要说明在使用时需要命名空间std::filesystem,以及如何操作路径等。 另外,要注意用户可能的实际应用场景,比如为什么需要这些新的头文件,能解决什么问题。例如,<filesystem>简化了文件操作,<optional>避免了使用裸指针或特殊值来表示可能缺失的值,提高代码安全性。 最后,提醒用户注意编译器的支持情况,比如C++17特性需要编译器支持相应的标准版本,如GCC 7及以上,Clang 5及以上,MSVC 2017等,并可能需要设置编译标志如-std=c++17。 现在需要将这些思路组织成中文回答,结构清晰,分点说明,确保准确性和实用性。</think>--- ### C++17 标准库新增头文件及关键变化 C++17 引入了多个新头文件,并对现有头文件进行了增强。以下是主要变化及新增头文件的详细介绍: --- ### 一、**新增头文件** #### 1. **`<filesystem>`** **作用**:提供跨平台文件系统操作接口。 **核心类/函数**: - `std::filesystem::path`:表示文件路径。 - `std::filesystem::directory_iterator`:遍历目录。 - `std::filesystem::create_directory`:创建目录。 - `std::filesystem::file_size`:获取文件大小。 **示例**: ```cpp #include <filesystem> namespace fs = std::filesystem; fs::path p = "data.txt"; if (fs::exists(p)) { std::cout << "文件大小: " << fs::file_size(p) << " 字节\n"; } ``` --- #### 2. **`<optional>`** **作用**:表示“可能有值”的容器,避免使用 `nullptr` 或特殊值标记无效状态。 **核心类**: - `std::optional<T>`:封装可能为空的 `T` 类型值。 **示例**: ```cpp #include <optional> std::optional<int> parse_int(const std::string& s) { try { return std::stoi(s); } catch (...) { return std::nullopt; // 表示无值 } } ``` --- #### 3. **`<variant>`** **作用**:实现类型安全的联合体(union),可存储多种类型的值。 **核心类/函数**: - `std::variant<Types...>`:可存储 `Types` 中的任意类型。 - `std::visit`:访问 `variant` 中的值。 **示例**: ```cpp #include <variant> #include <string> std::variant<int, std::string> data; data = 42; data = "Hello"; std::visit([](auto&& val) { std::cout << val << std::endl; }, data); ``` --- #### 4. **`<any>`** **作用**:存储任意类型的单值,支持类型安全的值提取。 **核心类**: - `std::any`:可存储任意类型的对象。 **示例**: ```cpp #include <any> std::any a = 42; a = std::string("Hello"); if (a.has_value() && a.type() == typeid(std::string)) { std::cout << std::any_cast<std::string>(a) << std::endl; } ``` --- #### 5. **`<string_view>`** **作用**:提供对字符串的非拥有(non-owning)视图,避免拷贝开销。 **核心类**: - `std::string_view`:轻量级字符串视图。 **示例**: ```cpp #include <string_view> void print(std::string_view sv) { std::cout << sv.substr(0, 5) << std::endl; // 无拷贝操作 } print("Hello, World!"); // 直接传递字面量 ``` --- #### 6. **`<memory_resource>`** **作用**:支持多态内存资源分配,用于实现自定义内存池。 **核心类**: - `std::pmr::memory_resource`:内存资源基类。 - `std::pmr::polymorphic_allocator`:多态分配器。 **示例**: ```cpp #include <memory_resource> #include <vector> std::pmr::monotonic_buffer_resource pool; std::pmr::vector<int> vec{&pool}; // 使用内存池分配元素 ``` --- #### 7. **`<charconv>`** **作用**:高性能的字符与数值转换。 **核心函数**: - `std::to_chars`:数值转字符串。 - `std::from_chars`:字符串转数值。 **示例**: ```cpp #include <charconv> char buffer[20]; int value = 42; auto [ptr, ec] = std::to_chars(buffer, buffer + 20, value); if (ec == std::errc{}) { std::cout << std::string_view(buffer, ptr - buffer); // 输出 "42" } ``` --- #### 8. **`<execution>`** **作用**:支持并行算法执行策略。 **核心内容**: - 执行策略:`std::execution::seq`(顺序)、`std::execution::par`(并行)、`std::execution::par_unseq`(并行+向量化)。 **示例**: ```cpp #include <algorithm> #include <execution> #include <vector> std::vector<int> data = {3, 1, 4, 1, 5}; std::sort(std::execution::par, data.begin(), data.end()); // 并行排序 ``` --- ### 二、**被弃用的头文件** 以下 C 标准库兼容头文件在 C++17 中被标记为弃用: - `<ccomplex>` - `<ciso646>` - `<cstdalign>` - `<cstdbool>` - `<ctgmath>` **原因**: - 这些头文件在 C++ 中用途有限(如 `<cstdbool>` 定义的 `bool` 类型已由 C++ 原生支持)。 - 建议直接使用 C++ 原生替代(如 `<complex>` 替代 `<ccomplex>`)。 --- ### 三、**现有头文件的重要增强** 1. **`<algorithm>`** - 新增算法:`std::clamp`(限制数值范围)、`std::sample`(采样元素)。 2. **`<numeric>`** - 新增:`std::gcd`(最大公约数)、`std::lcm`(最小公倍数)。 3. **`<iterator>`** - 弃用 `std::iterator`(用户应直接定义迭代器类型别名)。 --- ### 四、**使用注意事项** 1. **编译器支持**: - GCC 7+、Clang 5+、MSVC 2017 15.7+ 支持 C++17。 - 编译时需添加标志(如 `-std=c++17`)。 2. **文件系统库的链接**: - GCC/Clang 需链接 `-lstdc++fs` 或 `-lc++fs`(视编译器版本而定)。 3. **兼容性**: - 若需兼容旧代码,可通过 `#if __cplusplus >= 201703L` 条件编译。 --- ### 五、总结 C++17 新增的头文件(如 `<filesystem>`、`<optional>`)显著增强了标准库的功能,同时弃用了冗余的 C 兼容头文件。开发者应优先使用新特性以提升代码安全性和性能,并注意编译环境和迁移成本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值