6

C ++ 11標準には、一般コンテナ要件に次の行があります。

(23.2.1-3)

allocator_typeを宣言するこの節の影響を受けるコンポーネントの場合、これらのコンポーネントに格納されているオブジェクトは、allocator_traits ::construct関数を使用して構築され、allocator_traits :: destroy関数(20.6.8.2)を使用して破棄されます。これらの関数は、コンテナーの要素タイプに対してのみ呼び出され、コンテナーによって使用される内部タイプに対しては呼び出されません。

(23.2.1-7)

特に指定がない限り、この句で定義されているすべてのコンテナは、アロケータを使用してメモリを取得します

コンテナによって使用されるすべてのメモリが指定されたアロケータによって割り当てられるというのは本当ですか?標準では、内部型はallocator_traits :: Constructを使用せずに構築されるとされているため、演算子newを呼び出す必要があります。しかし、標準では、この句で定義されたすべてのコンテナは、アロケータを使用してメモリを取得するとも述べています。これは、私の意見では、通常の新しい演算子ではなく、配置の新しい演算子である必要があることを意味します。私は正しいですか?

例を示しましょう。なぜこれが重要なのか。

割り当てられたメモリを保持するクラスがあるとしましょう。

#include <unordered_map>
#include <iostream>
#include <cstdint>
#include <limits>
#include <memory>
#include <new>

class Arena
{
public:
        Arena(std::size_t size)
        {
                size_     = size;
                location_ = 0;

                data_ = nullptr;
                if(size_ > 0)
                        data_ = new(std::nothrow) uint8_t[size_];
        }
        Arena(const Arena& other) = delete;
        ~Arena()
        {
                if(data_ != nullptr)
                        delete[] data_;
        }
        Arena& operator =(const Arena& arena) = delete;

        uint8_t* allocate(std::size_t size)
        {
                if(data_ == nullptr)
                        throw std::bad_alloc();

                if((location_ + size) >= size_)
                        throw std::bad_alloc();

                uint8_t* result = &data_[location_];
                location_ += size;
                return result;
        }

        void clear()
        {
                location_ = 0;
        }

        std::size_t getNumBytesUsed() const
        {
                return location_;
        }

private:
        uint8_t* data_;
        std::size_t location_, size_;

};

カスタムアロケータもあります:

template <class T> class FastAllocator
{
public:
        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;

        template <class U> class rebind
        {
        public:
                typedef FastAllocator<U> other;

        };

        Arena* arena;

        FastAllocator(Arena& arena_): arena(&arena_) {}
        FastAllocator(const FastAllocator& other): arena(other.arena) {}
        template <class U> FastAllocator(const FastAllocator<U>& other): arena(other.arena) {}

        //------------------------------------------------------------------------------------
        pointer allocate(size_type n, std::allocator<void>::const_pointer)
        {
                return allocate(n);
        }
        pointer allocate(size_type n)
        {
                return reinterpret_cast<pointer>(arena->allocate(n * sizeof(T)));
        }

        //------------------------------------------------------------------------------------
        void deallocate(pointer, size_type) {}

        //------------------------------------------------------------------------------------
        size_type max_size() const
        {
                return std::numeric_limits<size_type>::max();
        }

        //------------------------------------------------------------------------------------
        void construct(pointer p, const_reference val)
        {
                ::new(static_cast<void*>(p)) T(val);
        }
        template <class U> void destroy(U* p)
        {
                p->~U();
        }

};

これが私たちの使い方です:

typedef std::unordered_map<uint32_t, uint32_t, std::hash<uint32_t>, std::equal_to<uint32_t>,
                           FastAllocator<std::pair<uint32_t, uint32_t>>> FastUnorderedMap;

int main()
{
        // Allocate memory in arena
        Arena arena(1024 * 1024 * 50);
        FastAllocator<uint32_t> allocator(arena);
        FastAllocator<std::pair<uint32_t, uint32_t>> pairAllocator(arena);
        FastAllocator<FastUnorderedMap> unorderedMapAllocator(arena);

        FastUnorderedMap* fastUnorderedMap = nullptr;

        try
        {
                // allocate memory for unordered map
                fastUnorderedMap = unorderedMapAllocator.allocate(1);

                // construct unordered map
                fastUnorderedMap =
                        new(reinterpret_cast<void*>(fastUnorderedMap)) FastUnorderedMap
                        (
                                0,
                                std::hash<uint32_t>(),
                                std::equal_to<uint32_t>(),
                                pairAllocator
                        );

                // insert something
                for(uint32_t i = 0; i < 1000000; ++i)
                        fastUnorderedMap->insert(std::make_pair(i, i));
        }
        catch(std::bad_alloc badAlloc)
        {
                std::cout << "--- BAD ALLOC HAPPENED DURING FAST UNORDERED MAP INSERTION ---" << std::endl;
        }

        // no destructor of unordered map is called!!!!
        return 0;
}

ご覧のとおり、unordered_mapのデストラクタは呼び出されませんが、アリーナオブジェクトの破棄中にメモリが解放されます。メモリリークは発生しますか?その理由は何ですか?

このトピックについての助けを本当にいただければ幸いです。

4

1 に答える 1

8

アロケータは4つの機能を提供することになっています(ここで興味深い):

  • 2はメモリ管理に使用されます:allocate/deallocate
  • 2はオブジェクトのライフタイム管理に使用されます:construct/destroy

引用のこれらの関数constructは、とdestroy(前の文で言及された)にのみ適用され、 allocate/deallocateには適用されないため、矛盾はありません。

ここで、メモリリークに関して、アリーナアロケータが機能するには、コンテナ内のオブジェクトがアリーナアロケータ(コンテナが保証する)を使用して構築されるだけでなく、それらのオブジェクトが割り当てるすべてのメモリもこのアロケータから取得する必要があります。残念ながら、これは少し複雑になる可能性があります。

于 2013-02-03T16:03:56.993 に答える