実装は意外と複雑です。要素 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 件のコメント:
コメントを投稿