【问题标题】:How are allocator in C++ implemented?C++中的分配器是如何实现的?
【发布时间】:2018-09-10 10:01:11
【问题描述】:

我试图在 C++ 中更好地理解std::allocator,我遇到了this question,在我看来实际上只有一个容器通常使用的分配器类(如std::vector)我的问题是如何这样的分配器实现了吗?它就像一个定期重新分配的堆栈吗?如果不是,它实际上是如何实现的?

【问题讨论】:

    标签: c++ memory-management


    【解决方案1】:

    默认分配器是std::allocator,只在需要时使用::operator new,所以没什么特别的。这与为所需的每个对象自己执行newdelete 大致相同。您可以在标准中的[default.allocator] 下阅读更多相关信息。

    分配器“接口”(实际上只是一组要求,在模板实例化期间强制执行)是这个过程的包装器,允许采用替代的内存供应方法。

    例如,您可能提供的替代分配器可以实现 memory pool 或其他特定于您需求的东西,从而减少诚实的动态分配。

    标准容器除了它们的元素类型外,还有一个分配器类型作为模板参数(您通常不会注意到这一点!),这就是您选择与该容器一起使用的替代实现的方式。

    在这些情况下,您通常会预先分配一些大块内存,然后在何时分配小块。从这个意义上说,这样的实现可以被认为是一种“堆中的堆”,但实际上根本没有理由需要给它提供堆语义。只需要遵守Allocator概念的要求即可。

    Josuttis 先生在http://www.josuttis.com/cppcode/allocator.html 上放了一个(无聊的)例子;我在这里复制它:

    /* The following code example is taken from the book
     * "The C++ Standard Library - A Tutorial and Reference"
     * by Nicolai M. Josuttis, Addison-Wesley, 1999
     *
     * (C) Copyright Nicolai M. Josuttis 1999.
     * Permission to copy, use, modify, sell and distribute this software
     * is granted provided this copyright notice appears in all copies.
     * This software is provided "as is" without express or implied
     * warranty, and with no claim as to its suitability for any purpose.
     */
    #include <limits>
    #include <iostream>
    
    namespace MyLib {
       template <class T>
       class MyAlloc {
         public:
           // type definitions
           typedef T        value_type;
           typedef T*       pointer;
           typedef const T* const_pointer;
           typedef T&       reference;
           typedef const T& const_reference;
           typedef std::size_t    size_type;
           typedef std::ptrdiff_t difference_type;
    
           // rebind allocator to type U
           template <class U>
           struct rebind {
               typedef MyAlloc<U> other;
           };
    
           // return address of values
           pointer address (reference value) const {
               return &value;
           }
           const_pointer address (const_reference value) const {
               return &value;
           }
    
           /* constructors and destructor
            * - nothing to do because the allocator has no state
            */
           MyAlloc() throw() {
           }
           MyAlloc(const MyAlloc&) throw() {
           }
           template <class U>
             MyAlloc (const MyAlloc<U>&) throw() {
           }
           ~MyAlloc() throw() {
           }
    
           // return maximum number of elements that can be allocated
           size_type max_size () const throw() {
               return std::numeric_limits<std::size_t>::max() / sizeof(T);
           }
    
           // allocate but don't initialize num elements of type T
           pointer allocate (size_type num, const void* = 0) {
               // print message and allocate memory with global new
               std::cerr << "allocate " << num << " element(s)"
                         << " of size " << sizeof(T) << std::endl;
               pointer ret = (pointer)(::operator new(num*sizeof(T)));
               std::cerr << " allocated at: " << (void*)ret << std::endl;
               return ret;
           }
    
           // initialize elements of allocated storage p with value value
           void construct (pointer p, const T& value) {
               // initialize memory with placement new
               new((void*)p)T(value);
           }
    
           // destroy elements of initialized storage p
           void destroy (pointer p) {
               // destroy objects by calling their destructor
               p->~T();
           }
    
           // deallocate storage p of deleted elements
           void deallocate (pointer p, size_type num) {
               // print message and deallocate memory with global delete
               std::cerr << "deallocate " << num << " element(s)"
                         << " of size " << sizeof(T)
                         << " at: " << (void*)p << std::endl;
               ::operator delete((void*)p);
           }
       };
    
       // return that all specializations of this allocator are interchangeable
       template <class T1, class T2>
       bool operator== (const MyAlloc<T1>&,
                        const MyAlloc<T2>&) throw() {
           return true;
       }
       template <class T1, class T2>
       bool operator!= (const MyAlloc<T1>&,
                        const MyAlloc<T2>&) throw() {
           return false;
       }
    }
    

    及用法:

    #include <vector>
    #include "myalloc.hpp"
    
    int main()
    {
        // create a vector, using MyAlloc<> as allocator
        std::vector<int,MyLib::MyAlloc<int> > v;
    
        // insert elements
        // - causes reallocations
        v.push_back(42);
        v.push_back(56);
        v.push_back(11);
        v.push_back(22);
        v.push_back(33);
        v.push_back(44);
    }
    

    【讨论】:

    • 但是容器和分配器是如何交互的呢?假设我有一个整数列表并且我想插入一个新的整数值,我会天真地使用new 来分配一个新节点并适当地链接它。分配器会做哪些不同的事情?
    • 默认分配器没有什么不同。您自己的替代方案可以返回一个指向预分配存储中某个位置的指针,或者一个指向存储在鸽子翅膀上的某个内存的指针
    • 有没有实际使用默认分配器的实例?
    • 如果你想从字面上看它是如何实现的,那么看看你的标准库的源代码,但老实说它不会很有趣。分配器具有遵守“给我一些内存”和“取回一些内存”的“功能”,就是这样
    • 标准容器默认使用默认分配器(实际上,它只是newdelete 的包装器)。但这是一个模板参数,因此您可以让它们使用您想要的任何东西。我添加了更多链接。
    【解决方案2】:

    分配器只是提供分配内存、释放内存、构造对象和销毁对象的策略。

    它们不提供重新分配内存的策略(增加先前分配的内存区域的大小)。所以在所有容器中,如果必须增加内存:

    • 容器要么分配一个新的内存块,
    • 或像vector 所做的那样,分配一个新的更大的内存区域,复制其中的旧元素,然后释放之前的内存区域。

    【讨论】:

    • 这个答案似乎更多的是关于容器如何请求内存,而不是分配器如何提供它。
    • @LightnessRacesinOrbit 所以我在回答这个问题而你没有??
    • 在我看来恰恰相反,但也许这就是我!
    猜你喜欢
    • 2017-04-09
    • 2018-07-09
    • 1970-01-01
    • 1970-01-01
    • 2013-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多