014: reserve()

reserve() を定義します。

実装は意外と複雑です。要素 T の特性によって領域確保の方法は、①realloc()を使う ②malloc()とムーブコンストラクタを使う ③malloc()のコピーコンストラクタを使う、という3通りを使い分けることになります。

template <typename T>
class myvector
{
...

private:
    struct trivially_copyable_tag {};
    struct move_constructible_tag {};
    struct copy_constructible_tag {};
    using realloc_switcher = typename std::conditional<
        std::is_trivially_copyable::value,
        trivially_copyable_tag,
        typename std::conditional<
            std::is_move_constructible::value,
            move_constructible_tag,
            copy_constructible_tag
        >::type
    >::type;
...

public:
    /**
     * @brief      Increase the capacity of the vector to a value that's greater or
     *             equal to new_cap. If new_cap is greater than the current capacity(),
     *             new storage is allocated, otherwise the method does nothing.
     *             reserve() does not change the size of the vector.
     *             If new_cap is greater than capacity(), all iterators, including the
     *             past-the-end iterator, and all references to the elements are
     *             invalidated. Otherwise, no iterators or references are invalidated.
     * @param[in]  new_cap: New capacity of the vector.
     * @throw      std::length_error: If new_cap is greater than max_size().
     * @throw      std::bad_alloc: If malloc() (or realloc()) fails to allocate storage.
     */
    void reserve(size_type new_cap)
    {
        if (new_cap > capacity_) { // expand capacity
            reallocation(length_check(new_cap), realloc_switcher());
        }
    }
...

private:
    /**
     * @brief      Does reallocation by calling reaclloc().
     *             value_type shall be TriviallyCopyable.
     * @param[in]  new_cap: New capacity of the vector. Should be new_cap > size_.
     * @param[in]  trivially_copyable_tag: Function switcher according to value_type characteristic.
     * @throw      std::bad_alloc: If realloc() fails to allocate storage.
     */
    void reallocation(size_type new_cap, trivially_copyable_tag)
    {
        // reallocation by calling realloc()
        void* p = realloc(static_cast(heap_), sizeof(value_type) * new_cap);
        if (p == nullptr) {
            throw std::bad_alloc();
        }

        // set new values to member variables
        if (static_cast(p) != heap_) {
            heap_ = static_cast(p);
        }
        capacity_ = new_cap;
    }

    /**
     * @brief      Does reallocation by calling malloc().
     *             value_type shall be MoveConstructible.
     * @param[in]  new_cap: New capacity of the vector. Should be new_cap > size_.
     * @param[in]  move_constructible_tag: Function switcher according to value_type characteristic.
     * @throw      std::bad_alloc: If malloc() fails to allocate storage.
     */
    void reallocation(size_type new_cap, move_constructible_tag)
    {
        // new allocation
        pointer p = mymalloc(new_cap); // may throw std::bad_alloc

        // copy to new address by move-constructor, and clean up
        for (size_type i = 0; i < size_; i++) {
            new(&p[i]) value_type(std::move(heap_[i]));
            heap_[i].~value_type();
        }
        free(heap_);

        // set new values to member variables
        heap_ = p;
        capacity_ = new_cap;
    }

    /**
     * @brief      Does reallocation by calling malloc().
     *             value_type shall be CopyConstructible.
     * @param[in]  new_cap: New capacity of the vector. Should be new_cap > size_.
     * @param[in]  copy_constructible_tag: Function switcher according to value_type characteristic.
     * @throw      std::bad_alloc: If malloc() fails to allocate storage.
     */
    void reallocation(size_type new_cap, copy_constructible_tag)
    {
        // new allocation
        pointer p = mymalloc(new_cap); // may throw std::bad_alloc

        // copy to new address by copy-constructor, and clean up
        for (size_type i = 0; i < size_; i++) {
            new(&p[i]) value_type(heap_[i]);
            heap_[i].~value_type();
        }
        free(heap_);

        // set new values to member variables
        heap_ = p;
        capacity_ = new_cap;
    }
...

};
まず、領域確保の方法を分けるための内部クラス3つ trivially_copyable_tag、move_constructible_tag、copy_constructible_tag を定義します。そして、型名realloc_switcher に、要素 T が trivial であれば trivially_copyable_tag を、要素 T が非trivial でムーブコンストラクトが可能なら move_constructible_tag を、要素 T がそれ以外(非trivial でコピーコンストラクト可能のはず)なら copy_constructible_tag を、それぞれ割り当てます。

reserve()関数の中身は、まず指定された new_cap が現在の capacity よりも大きいかどうかをチェックし、大きければ reallocation()関数を呼びます。ここで、reallocation()の第二引数として realloc_switcher を渡します。第二引数の型によって、3つの reallocation() の内どれが呼ばれるかが決定されます。

reallocation() は第二引数の型によって動作が異なります。
第二引数が trivially_copyable_tag であれば、T は trivial なので realloc()関数を使う最も簡単な実装になります。
第二引数が move_constructible_tagであれば、T は 非trivial なので、malloc()、free()、placement new によるコピー、明示的なデストラクタ、などの処理を自前ですることになります。また、T はムーブコンストラクタ可能なので、placement new によるコピーの際、std::move() を使ってより効率的なムーブコンストラクタが呼ばれます。
第二引数が copy_constructible_tag であれば、T は 非trivial なので、malloc()、free()、placement new によるコピー、明示的なデストラクタ、などの処理を自前ですることになります。また、T はムーブコンストラクタ不可なので、placement new によるコピーの際、コピーコンストラクタが呼ばれます。


全ソースコード: https://github.com/suomesta/myvector/tree/master/014

0 件のコメント:

コメントを投稿