[lv5] ft_containers(1/6) vectorチュートリアル

[(0/6)はじめにもどる] https://note.com/syamashi/n/nd36a889a1166
[(2/6)サブ関数にすすむ] https://note.com/syamashi/n/na27f05d42ac8

0. 江添亮のC++入門。
vectorだけやればいいかと思ったが、array編の実装を引き継いだ話になるので、ここから読みます。
・std::array
https://cpp.rainy.me/020-array.html

読み終えたら、いよいよvectorの実装。ここで書くコードは提出まで使います。ここから
・vectorの実装 : 基礎 簡易vectorの概要
https://cpp.rainy.me/033-vector-implementation.html#%E7%B0%A1%E6%98%93vector%E3%81%AE%E6%A6%82%E8%A6%81

・コピー
https://cpp.rainy.me/035-copy.html
ここまで写経。

ちなみに、c++11以降の機能/関数を実装してはいけないので、以下の写経はスキップします。

1. move
2. void shrink_to_fit()
3. 初期化子リスト: vector<int> V = {1,2,3};


コピーチュートリアルまで写経しても、脱字や環境差異によって動きません。4点修正し、動くようにしたものがこちら。

containers/vector.hpp

#ifndef VECTOR_HPP
#define VECTOR_HPP

#include <iostream>
#include <iterator>
#include <limits>

namespace ft {

template <typename T, typename Allocator = std::allocator<T> >
class vector {
public:
 // value_typeなどネストされた型名
 using value_type = T;
 using pointer = T*;
 using const_pointer = const pointer;
 using reference = value_type&;
 using const_reference = const value_type&;
 using allocator_type = Allocator;
 using size_type = std::size_t;
 using difference_type = std::ptrdiff_t;

 using iterator = pointer;
 using const_iterator = const_pointer;
 using reverse_iterator = std::reverse_iterator<iterator>;
 using const_reverse_iterator = std::reverse_iterator<const_iterator>;

 // コンストラクター
 vector() : vector(allocator_type()) {}
 vector(const allocator_type& alloc) noexcept
     : first(NULL), last(NULL), reserved_last(NULL), alloc(alloc) {}

 explicit vector(size_type count, const T& value = T(),
                 const Allocator& alloc = Allocator())
     : vector(alloc) {
   resize(count, value);
 }

 template <typename InputIterator>
 vector(InputIterator first, InputIterator last,
        const Allocator& allocator = Allocator(),
        typename std::enable_if<!std::is_integral<InputIterator>::value,
                                InputIterator>::type* = NULL)
     : first(NULL), last(NULL), reserved_last(NULL), alloc(allocator) {
   reserve(std::distance(first, last));
   for (auto i = first; i != last; ++i) {
     push_back(*i);
   }
 }

 // デストラクター
 ~vector() {
   // 1. 要素を末尾から先頭に向かう順番で破棄
   clear();
   // 2. 生のメモリーを解放する
   deallocate();
 }
 // コピー
 vector(const vector& r)
     : first(NULL),
       last(NULL),
       reserved_last(NULL),
       alloc(traits::select_on_container_copy_construction(r.alloc)) {
   // コピー元の要素数を保持できるだけのストレージを確保
   reserve(r.size());
   // コピー元の要素をコピー構築
   // destはコピー先
   // [src, last)はコピー元
   for (auto dest = first, src = r.begin(), last = r.end(); src != last;
        ++dest, ++src) {
     construct(dest, *src);
   }
   last = first + r.size();
 }

 vector& operator=(const vector& r) {
   // 1. 自分自身への代入なら何もしない
   if (this == &r) return *this;

   // 2. 要素数が同じならば
   if (size() == r.size()) {  // 要素ごとにコピー代入
     std::copy(r.begin(), r.end(), begin());
   }
   // 3. それ以外の場合で
   else
       // 予約数が十分ならば、
       if (capacity() >= r.size()) {
     // 有効な要素はコピー
     std::copy(r.begin(), r.begin() + r.size(), begin());
     // 残りはコピー構築
     for (auto src_iter = r.begin() + r.size(), src_end = r.end();
          src_iter != src_end; ++src_iter, ++last) {
       construct(last, *src_iter);
     }
   }
   // 4. 予約数が不十分ならば
   else {
     // 要素をすべて破棄
     destroy_until(rbegin());
     // 予約
     reserve(r.size());
     // コピー構築
     for (auto src_iter = r.begin(), src_end = r.end(), dest_iter = begin();
          src_iter != src_end; ++src_iter, ++dest_iter, ++last) {
       construct(dest_iter, *src_iter);
     }
   }
   return *this;
 }
 // 容量確認
 size_type size() const noexcept { return end() - begin(); }
 bool empty() const noexcept { return begin() == end(); }
 size_type capacity() const noexcept { return reserved_last - first; }

 // 要素アクセス

 void push_back(const_reference value) {
   // 予約メモリーが足りなければ拡張
   if (size() + 1 > capacity()) {
     // 現在のストレージサイズ
     auto c = size();
     // 0の場合は1に
     if (c == 0)
       c = 1;
     else
       // それ以外の場合は2倍する
       c *= 2;

     reserve(c);
   }
   construct(last, value);
   ++last;
 }

 reference operator[](size_type i) { return first[i]; }
 const_reference operator[](size_type i) const { return first[i]; }
 reference at(size_type i) {
   if (i >= size()) throw std::out_of_range("index is out of range.");

   return first[i];
 }
 const_reference at(size_type i) const {
   if (i >= size()) throw std::out_of_range("index is out of range.");

   return first[i];
 }

 reference front() { return *first; }
 const_reference front() const { return *first; }
 reference back() { return *(last - 1); }
 const_reference back() const { return *(last - 1); }

 pointer data() noexcept { return first; }
 const_pointer data() const noexcept { return first; }

 // イテレーターアクセス
 iterator begin() noexcept { return first; }
 iterator end() noexcept { return last; }
 iterator begin() const noexcept { return first; }
 iterator end() const noexcept { return last; }
 const_iterator cbegin() const noexcept { return first; }
 const_iterator cend() const noexcept { return last; }
 reverse_iterator rbegin() noexcept { return reverse_iterator{last}; }
 reverse_iterator rend() noexcept { return reverse_iterator{first}; }
 const_reverse_iterator rbegin() const noexcept {
   return reverse_iterator{last};
 }
 const_reverse_iterator rend() const noexcept {
   return reverse_iterator{first};
 }

 void clear() noexcept { destroy_until(rend()); }

 /*
 1. すでに指定された要素数以上に予約されているなら何もしない
 2. まだ動的メモリー確保が行われていなければ動的メモリー確保をする
 3. 有効な要素がある場合は新しいストレージにコピーする
 */
 void reserve(size_type sz) {
   // すでに指定された要素数以上に予約されているなら何もしない
   if (sz <= capacity()) return;

   // 動的メモリー確保をする
   auto ptr = allocate(sz);

   // 古いストレージの情報を保存
   auto old_first = first;
   auto old_last = last;
   auto old_capacity = capacity();

   // 新しいストレージに差し替え
   first = ptr;
   last = first;
   reserved_last = first + sz;

   // 例外安全のため
   // 関数を抜けるときに古いストレージを破棄する
   //    std::scope_exit e(
   //        [&] { traits::deallocate(alloc, old_first, old_capacity); });

   // 古いストレージから新しいストレージに要素をコピー構築
   // 実際にはムーブ構築
   for (auto old_iter = old_first; old_iter != old_last; ++old_iter, ++last) {
     // このコピーの理解にはムーブセマンティクスの理解が必要
     construct(last, std::move(*old_iter));
   }

   // 新しいストレージにコピーし終えたので
   // 古いストレージの値は破棄
   for (auto riter = reverse_iterator(old_last),
             rend = reverse_iterator(old_first);
        riter != rend; ++riter) {
     destroy(&*riter);
   }
   // scope_exitによって自動的にストレージが破棄される
   alloc.deallocate(old_first, old_capacity);
 }

 /*
 1. 現在の要素数より少なくリサイズする場合、末尾から要素を破棄する
 2. 現在の要素数より大きくリサイズする場合、末尾に要素を追加する
 3. 現在の要素数と等しくリサイズする場合、何もしない
 */
 void resize(size_type sz) {
   // 現在の要素数より少ない
   if (sz < size()) {
     auto diff = size() - sz;
     destroy_until(rbegin() + diff);
     last = first + sz;
   }
   // 現在の要素数より大きい
   else if (sz > size()) {
     reserve(sz);
     for (; last != reserved_last; ++last) {
       construct(last);
     }
   }
 }

 void resize(size_type sz, const_reference value) {
   // 現在の要素数より少ない
   if (sz < size()) {
     auto diff = size() - sz;
     destroy_until(rbegin() + diff);
     last = first + sz;
   }
   // 現在の要素数より大きい
   else if (sz > size()) {
     reserve(sz);
     for (; last != reserved_last; ++last) {
       construct(last, value);
     }
   }
 }

private:
 // 先頭の要素へのポインター
 pointer first;
 // 最後の要素の1つ前方のポインター
 pointer last;
 // 確保したストレージの終端
 pointer reserved_last;
 // アロケーターの値
 allocator_type alloc;

 // ヘルパー関数
private:
 using traits = std::allocator_traits<allocator_type>;
 pointer allocate(size_type n) { return traits::allocate(alloc, n); }
 void deallocate() { traits::deallocate(alloc, first, capacity()); }
 void construct(pointer ptr) { traits::construct(alloc, ptr); }
 void construct(pointer ptr, const_reference value) {
   traits::construct(alloc, ptr, value);
 }
 // ムーブ用
 void construct(pointer ptr, value_type&& value) {
   traits::construct(alloc, ptr, std::move(value));
 }
 void destroy(pointer ptr) { traits::destroy(alloc, ptr); }
 void destroy_until(reverse_iterator rend) {
   for (auto riter = rbegin(); riter != rend; ++riter, --last) {
     destroy(&*riter);
   }
 }
};

};  // namespace ft

#endif


編集は4点。
1. constructerをcppreference準拠に変更。

1. cppreference に準拠

  // コンストラクター
 vector() : vector(allocator_type()) {}

 vector(const allocator_type& alloc) noexcept
     : first(NULL), last(NULL), reserved_last(NULL), alloc(alloc) {}

 explicit vector(size_type count, const T& value = T(),
                 const Allocator& alloc = Allocator())
     : vector(alloc) {
   resize(count, value);
 }

 template <typename InputIterator>
 vector(InputIterator first, InputIterator last,
        const Allocator& allocator = Allocator(),
        typename std::enable_if<!std::is_integral<InputIterator>::value,
                                InputIterator>::type* = NULL)
     : first(NULL), last(NULL), reserved_last(NULL), alloc(allocator) {
   reserve(std::distance(first, last));
   for (auto i = first; i != last; ++i) {
     push_back(*i);
   }
 }

Q. cppreference
A. cppの公式サイト。
例えば、以下は std::vector の constructer 定義が記載されています。
https://en.cppreference.com/w/cpp/container/vector/vector

Q. どう読めばいい
A. まず関数のバージョンから見ます。
今課題で実装すべきは、c++11より前バージョンの関数。以下5つです。

(1) vector(); (until c++17)
(2) explicit vector( const Allocator& alloc ); (until C++17)
(3) explicit vector( size_type count,
                const T& value = T(),
                const Allocator& alloc = Allocator()); (until C++11)
(5) template< class InputIt >
vector( InputIt first, InputIt last, 
       const Allocator& alloc = Allocator() ); (until C++20)
(6) vector( const vector& other ); (until C++20)

Q.  (5) typename std::enable_if<!std::is_integral<InputIterator>::value,
InputIterator>::type* = NULL
A. 引数templateの、InputIteratorについて。
なんと、Iterator認識してくれません。なので、
vector<int>(1, 2);
が(5) template <typename InputIterator> に入ってしまいます。
std::enable_ifを使用することで、Iteratorのみ正しくオーバーロードされるようになります。後ほど記載するので今は写経。

2. std::scope_exitを削除

void reserve(size_type sz) {
  ...
  // scope_exitによって自動的にストレージが破棄されるところ、されなくなるので、deallocateを追記
  alloc.deallocate(old_first, old_capacity);
}

3. referenceの型あわせ

 reference front() { return *first; }
 const_reference front() const { return *first; }
 reference back() { return *(last - 1); }
 const_reference back() const { return *(last - 1); }

4. destroy_allではない

 destroy_until(rbegin()); // destory_all()

次。テストケースはこちら。
これは消去予定。コンパイルできるかどうか、ぐらいの確認で適当。
testfiles/main.cpp

#include "tester.hpp"

int main() {
  tutorial_test();
}

testfiles/tutorial.cpp

#include "tester.hpp"
#include <array>

template <typename T>
void pout(T s) {
 static int no;
 cout << endl;
 cout << "--- [" << ++no << "]:" << s << " ---" << endl;
}

template <class T>
void vdebug(T& V) {
 cout << "size:" << V.size() << " capacity:" << V.capacity() << endl;
 cout << "{ ";
 for (typename T::iterator it = V.begin(); it != V.end(); ++it)
   cout << *it << " ";
 cout << "}" << endl;
}

void tutorial_test() {
 {
   ft::vector<int> v(1);
   // ft::vector<int>::iterator
   auto i = v.begin();
   // OK、代入可能
   *i = 0;
   // constなvectorへのリファレンス
   auto const& cv = v;
   // ft::vector<int>::const_iterator ここがとれない
   auto ci = cv.begin();
   // エラー
   // const_iteratorを参照した先には代入できない
   cout << *ci << endl;
 }


 {
   ft::vector<int> v(1);
   // ft::vector<int>::const_iterator
   auto i = v.cbegin();
   cout << *i << endl;
 }

 {
   ft::vector<int> v;
   for (int i = 1; i <= 5; ++i) v.push_back(i);

   // イテレーター
   auto i = v.begin();
   cout << *i << endl;  // 1

   // リバースイテレーター
   auto r = v.rbegin();
   cout << *r << endl;  // 5
 }

 {
   ft::vector<int> v;
   // true、要素数0
   bool a = v.empty();
   cout << a << endl;
   v.push_back(0);
   // false、要素数非ゼロ
   bool b = v.empty();
   cout << b << endl;
   // 1、現在の要素数
   auto s = v.size();
   cout << s << endl;
   // 実装依存、追加の動的メモリー確保をせずに格納できる要素の最大数
   auto c = v.capacity();
   cout << c << endl;
 }

 {
   ft::vector<int> v;
   for (int i = 1; i <= 5; ++i) v.push_back(i);
   v[1];  // 2
   v[3];  // 4
 }

 {
   try {
     // 有効なインデックスはv[0]からv[4]まで
     ft::vector<int> v;
     for (int i = 1; i <= 5; ++i) v.push_back(i);

     v[0] = 0;  // OK
     v[3] = 0;  // OK
     v[5] = 0;  // エラー
   } catch (std::out_of_range e) {
     cout << e.what();
   }
 }

 {
   ft::vector<int> v;
   for (int i = 1; i <= 5; ++i) v.push_back(i);
   v.front();  // 1
   v.back();   // 5
 }

 {
   ft::vector<int> v;
   for (int i = 1; i <= 3; ++i) v.push_back(i);
   int* ptr = v.data();
   cout << *ptr << endl;  // 1
 }

 {
   ft::vector<int> v;
   cout << v.empty() << endl;  // true
 }

 {
   ft::vector<int> v(100);
   cout << v.size() << endl;  // 100
 }

 {
   ft::vector<int> v(100, 123);
   v[0];   // 123
   v[12];  // 123
   v[68];  // 123
 }

 {
   ft::vector<int> v;
   v.resize(10);
   v.size();  // 10
   // 減らす
   v.resize(5);
   v.size();  // 5
 }

 {
   ft::vector<int> v;
   v.resize(3, 123);
   // vは{123,123,123}
 }

 {
   ft::vector<int> v;
   for (int i = 1; i <= 5; ++i) v.push_back(i);
   v.resize(3);
   // vは{1,2,3}
 }

 {
   ft::vector<int> v;
   // vは{}
   v.push_back(1);
   // vは{1}
   v.push_back(2);
   // vは[1,2}
   v.push_back(3);
   // vは{1,2,3}
 }

 {
   ft::vector<int> v;
   // 少なくとも3個の要素を追加できるように動的メモリー確保
   v.reserve(3);
   v.size();      // 0
   v.capacity();  // 3以上

   // 動的メモリー確保は発生しない
   v.push_back(1);
   v.push_back(2);
   v.push_back(3);
   // 動的メモリー確保が発生する可能性がある。
   v.push_back(3);
 }

 {
   ft::vector<int> v;
   for (int i = 1; i <= 3; ++i) v.push_back(i);
   v.clear();
   v.size();  // 0
 }

 {
   std::allocator<int> alloc;
   // 空
   ft::vector<int> v1(alloc);
   // 要素数5
//    ft::vector<int> v2(5, alloc);
   // 要素数5で初期値123
   ft::vector<int> v3(5, 123, alloc);
 }

 {
   // 要素数5
   ft::vector<int> v;
   for (int i = 1; i <= 5; ++i) v.push_back(i);
   // 3個の要素を保持できるよう予約
   v.reserve(3);
   // 無視する
 }

 {
   ft::vector<int> v;
   // おそらく動的メモリー確保
   v.reserve(10000);
 }

 {
   // 要素数3
   ft::vector<int> v;
   for (int i = 1; i <= 3; ++i) v.push_back(i);
   // 1万個の要素を保持できるだけのメモリーを予約
   v.reserve(10000);
   // vは{1,2,3}
 }

 {
   // 要素数0
   ft::vector<int> v;
   // 要素数10
   v.resize(10);
   // 要素数5
   v.resize(5);
   // 要素数変わらず
   v.resize(5);
 }

 {
   ft::vector<int> v ;
   for(int i=1; i<=3; ++i) v.push_back(i);
   v.resize(5, 4);
   // vは{1,2,3,4,4}
 }

 {
   // 要素数5
   ft::vector<int> v(5);
   v.resize(5);  // 何もしない
 }

 {
   ft::vector<int> v(10, 1);
   v[2] = 99;
   v.resize(5);
   // vは{1,1,99,1,1}
 }

 {
   ft::vector<int> v;
   // vは{}
   v.push_back(1);
   // vは{1}
   v.push_back(2);
   // vは{1,2}
 }

 {
   std::array<int, 5> a{1, 2, 3, 4, 5};
   ft::vector<int> v(std::begin(a), std::end(a));
   // vは{1,2,3,4,5}
 }

 {
   ft::vector<int> v;
   for (int i = 1; i <= 5; ++i) v.push_back(i);
   ft::vector<int> w = v;
   // wは{1,2,3,4,5}
 }
}

江添int main()のうち、テストケースを2つ除外します。
1.1) const_iteratorにならない。

  {
   ft::vector<int> v(1);
   // ft::vector<int>::iterator
   auto i = v.begin();
   // OK、代入可能
   *i = 0;
   // constなvectorへのリファレンス
   auto const& cv = v;
   // ft::vector<int>::const_iterator
   auto ci = cv.begin();
   // エラー
   // const_iteratorを参照した先には代入できない
   cout << *ci << endl;
 }

最後の *ci = 0; を対象外。
random_access_iteratorを実装したら、const_iteratorとして読まれるようになります。

1.2) c++11なので実装対象外

ft::vector<int> v2(5, alloc);
                ^  ~~~~~~~~
tutorial.cpp:184:21: error: no matching constructor for initialization of 'ft::vector<int>'

・tutorial写経->c++11でコンパイルできるところまで。
newgameフォルダで仕事してます。
https://github.com/syamashi/ft_container/tree/c593b635ab4c2b5f46f61b81bcf5285bcb60790b/newgame

コンパイル成功でようやく一息。

vectorに必要な関数を先に実装します。

[その他関数にすすむ] https://note.com/syamashi/n/na27f05d42ac8


この記事が気に入ったらサポートをしてみませんか?