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.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值