[C ++テンプレート] -STLベクトルコンテナーベクトルとコンテナー空間アロケーターの実装

1. STLベクトルコンテナーベクトルの単純な実装

1.コンテナーの定義
まず、コンテナーとは何かを理解しましょう。彼の書いた説明はBaiduです。ここでは、一般的な方法で説明します。コンテナーは、他のオブジェクトを保持するオブジェクトです。そして、このオブジェクトには、他のオブジェクトを処理するメソッドがあります。

C ++はテンプレートベースのアプローチを使用してコンテナーを処理し、STLのコンテナーはさまざまなデータ構造を提供します。

2.ベクトルコンテナー
(1)は
、線形順次構造として定義されます。配列と同じですが、サイズを指定せずに自動的に拡張できます。配列のように操作できますが、その特徴から、完全にベクトルを動的配列として扱うことができます。

(2)特徴

  • 任意の要素に直接アクセスできます。
  • 線形順次構造。連続するスペースを指定することも、サイズを事前に指定することも、スペースを自動的に拡張することも、配列のように操作することもできます。つまり、[]演算子とvector.at()がサポートされているため、通常は動的配列に反映されます。データpush_back()を追加し、終了データpop_back()を削除します。
  • 割り当てられたスペースが十分でない場合、ベクトルはより大きなメモリブロックに適用され(2の倍数で増加)、元のデータを新しいメモリブロックにコピーし、元のメモリブロック内のオブジェクトを破棄して、最後に元のメモリスペースを解放します。したがって、ベクターに格納されるデータの量が非常に多い場合は、パフォーマンスを消費するため、ベクターのサイズが事前にわかっている場合はパフォーマンスが最適になります。
  • 省スペース。継続的に保存されるため、データが保存されている領域に無駄がありませんが、実際にはほとんどの場合、データがいっぱいではないため、実際に保存されていない領域は無駄になります
  • 内部での操作の挿入と削除の効率は低いです。ベクトル内のシーケンシャルなテーブル構造設計により、このような操作は基本的に禁止されており、バックエンドでのみ追加および削除操作を実行するように設計されています。
    彼のレイアウトは次のとおりです:
    ここに画像の説明を挿入
    彼の特定のコード実装を見てみましょう:
 template<typename T>
class vector
{
public:
	vector(int size = 10)
	{
		_first = new T[size];
		_last = _first;
		_end = _first + size;
	}
	~vector()
	{
		delete[] _first;
		_first = _last = _end = nullptr;
	}
	vector(const vector<T>& rhs)
	{
		int size = rhs._end - rhs._first;
		_first = new T[size];
		//拷贝有效元素
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			_first[i] = rhs._first[i];
		}
		_last = _first + len;
		_end = _first + size;
	}
	vector<T>& operator = (const vector<T>& rhs)
	{
		if (this == &rhs)
			return *this;

		delete[] _first;

		int size = rhs._end - rhs._first;
		_first = new T[size];
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			_first[i] = rhs._first[i];
		}
		_last = _first + len;
		_end = _first + size;
		return *this;
	}
	void push_back(const T &val)//向容器末尾添加元素
	{
		if (full())
			expand();
		*_last++ = val;
	}
	void pop_back()//从容器末尾删除元素
	{
		if (empty())
			return;
		--_last;
	}
	T back() const//返回容器末尾的元素的值
	{
		return *(_last - 1);
	}
	bool full()const { return _last == _end; }
	bool empty()const { return _first == _last; }
	int size()const { return _last - _first; }
private:
	T* _first;//指向数组起始的位置
	T* _last;//指向数组中有效元素的后继位置
	T* _end;//指向数组空间的后继位置
	void expand()//容器的二倍扩容
	{
		int size = _end - _first;
		T* ptmp = new T[2 * size];
		for (int i = 0; i < size; ++i)
		{
			ptmp[i] = _first[i];
		}
		delete[]_first;
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2 * size;
	}
};
int main()
{
	vector<int> vec;
	for (int i = 0; i < 20; ++i)
	{
		vec.push_back(rand() % 100);
	}
	//vec.pop_back();
	while (!vec.empty())
	{
		cout << vec.back() << " ";
		vec.pop_back();
	}
	cout << endl;
	return 0;
} 

結果は次のとおりです。
ここに画像の説明を挿入

第二に、コンテナー空間アダプターアロケーターの簡単な実装

上記のベクターコンテナーの実装には、スペースアロケーターと呼ばれるものがありません。
最初に、C ++標準ライブラリで定義されたベクターを見てみましょう

template<class _Ty,
	class _Alloc = allocator<_Ty> >
	class vector
		: public _Vector_alloc<!is_empty<_Alloc>::value,
			_Vec_base_types<_Ty, _Alloc> >

次に、上記のベクターコンテナーの何が問題になっているのか考えてみましょう。
質問1:
Testオブジェクトを使用してインスタンス化すると、実行結果はどうなりますか?
テストは次のように実装されます。

class Test
{
public:
	Test(){cout << "Test()" << endl;}
	~Test(){cout << "~Test" << endl;}
};

実行結果は次のとおりです。実行結果の
ここに画像の説明を挿入
意味:
コンストラクターを作成するとサイズが10になるため、この場所では空のコンテナーですが、10個のTestオブジェクトを作成し、10回破壊しています。newは構築に使用されるため、スペースを開くだけでなく、オブジェクトも構築するため、10個のオブジェクトが構築されます

エラーの原因:

  1. コンテナオブジェクトを定義する場合、最下層は宇宙開発専用であり、オブジェクトの構築用ではありません。ただし、newを使用すると、両方の機能が同時に実現されます。
  2. 構造化解除中に削除が使用され、_firstポインターが指す配列の各要素が有効なテストオブジェクトとして破棄されました。配列は非常に長い可能性がありますが、有効な要素はわずかしか存在しない可能性があります。破棄する場合、有効な要素のみを破棄し、配列メモリ全体を解放します。

注意深く考えても、上記のコードにはまだ問題があります
問題2:
次のコードを実行します。

int main()
{
	Test t1, t2, t3;
	cout << "-------------------------" << endl;
	vector<Test> vec;
	vec.push_back(t1);
	vec.push_back(t2);
	vec.push_back(t3);
	cout << "-------------------------" << endl;
	vec.pop_back();
	cout << "-------------------------" << endl;
	
	return 0;
}


ここに画像の説明を挿入
操作結果の意味は次のとおりです。操作結果の意味:
下部はnewを使用し、テストオブジェクトは実際には各位置に配置されます。push_backが既存のテストオブジェクトに値を割り当てることと同等の場合、問題が発生します。

ただし、deleteを使用して破棄することはできません。deleteは、デストラクターを呼び出すだけでなく、無料の操作も実行します。破棄するだけで、配列のヒープメモリを解放しないでください。

解決策:

1.メモリ開発とオブジェクト構築を個別に分離する
2.コンテナの有効な要素を破棄し、_firstポインタが指すヒープメモリを解放する
3.オブジェクトを破棄するだけで、オブジェクトの破棄とメモリを分離する

コンテナーのスペースアロケーター:スペースアロケーターのコア機能は、オブジェクトメモリの開発とオブジェクトの構築のプロセス、およびオブジェクトの構造化とメモリの解放のプロセスを分解することです。コンテナの基礎となるメモリの開発、メモリの解放、オブジェクトの構築、破棄はすべて、アロケータスペースアダプタを介して行われます。
特定のコードは次のように実装されます。

template<typename T>
struct Allocator
{
	T* allocate(size_t size)//负责内存开辟
	{
		return (T*)malloc(sizeof(T) * size);
	}
	void deallocate(void* p)//负责内存释放
	{
		free(p);
	}
	void construct(T* p, const T& val)//负责对象构造
	{
		new(p) T(val);//定位new
	}
	void destory(T* p)//负责对象析构
	{
		p->~T();//~T()代表了T类型的析构函数
	}
};
/*
容器低沉内存开辟,内存释放,对象构造析构,都通过allocator空间配置器来实现
*/
template<typename T,typename Alloc = Allocator<T>>
class vector
{
public:
	vector(int size = 10)
	{
		//需要把内存开辟和对象构造处理
		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;
	}
	~vector()
	{
		//析构容器有效的元素,然后释放_first指针指向的堆内存
		for (T* p = _first; p != _last; ++p)
		{
			_allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作
		}
		_allocator.deallocate(_first);//释放堆上的数组内存
		_first = _last = _end = nullptr;
	}
	vector(const vector<T>& rhs)
	{
		int size = rhs._end - rhs._first;
		//拷贝有效元素
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			_allocator.construct(_first + i, rhs._first[i]);
		}
		_last = _first + len;
		_end = _first + size;
	}
	vector<T>& operator = (const vector<T>& rhs)
	{
		if (this == &rhs)
			return *this;
		
		for (T* p = _first; p != _last; ++p)
		{
			_allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作
		}
		_allocator.deallocate(_first);//释放堆上的数组内存

		int size = rhs._end - rhs._first;
		_first = new T[size];
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			_first[i] = rhs._first[i];
		}
		_last = _first + len;
		_end = _first + size;
		return *this;
	}
	void push_back(const T &val)//向容器末尾添加元素
	{
		if (full())
			expand();
		_allocator.construct(_last, val);
		_last++;
	}
	void pop_back()//从容器末尾删除元素
	{
		if (empty())
			return;
		//--_last;不仅要把_last指针--,还需要析构删除的元素
		--_last;
		_allocator.destory(_last);
	}
	T back() const//返回容器末尾的元素的值
	{
		return *(_last - 1);
	}
	bool full()const { return _last == _end; }
	bool empty()const { return _first == _last; }
	int size()const { return _last - _first; }
private:
	T* _first;//指向数组起始的位置
	T* _last;//指向数组中有效元素的后继位置
	T* _end;//指向数组空间的后继位置
	Alloc _allocator;//定义容器的空间配置器对象
	void expand()//容器的二倍扩容
	{
		int size = _end - _first;
		//T* ptmp = new T[2 * size];
		T* ptmp = _allocator.allocate(2 * size);
		for (int i = 0; i < size; ++i)
		{
			_allocator.construct(ptmp + i, _first[i]);
			//ptmp[i] = _first[i];
		}
		delete[]_first;
		for (T* p = _first; p != _last; ++p)
		{
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2 * size;
	}
};
class Test
{
public:
	Test(){ cout << "Test()" << endl; }
	~Test() { cout << "Test()" << endl; }

};
公開された98元の記事 ウォンの賞賛9 ビュー3670

おすすめ

転載: blog.csdn.net/qq_43412060/article/details/105123315