C++primer第2回読書学習ノート(第18章:特殊ツールと技術:メモリ割り当ての最適化)

10022 ワード

第十八章:特殊なツールと技術:メモリの分配を最適化する
C++タイプ割り当ては、new固有のタイプのメモリを割り当て、新しい割り当てられたメモリにオブジェクトを構築するタイプ化操作です.New式は、動的に割り当てられたクラスごとに構造関数を自動的に呼び出します.ただし、メモリ割り当てとオブジェクト構造を分ける必要がある場合があります.予め割り当てられているが、後で使用しないオブジェクトを構築するのは無駄です.
メモリ割り当ては、オブジェクトプロファイルとメモリ回収のように、オブジェクト構造と密接に絡み合っています.New式はメモリを割り当て、そのメモリにオブジェクトを構築し、delete式は構造関数を呼び出してオブジェクトを取り消し、使用したメモリをシステムに返します.
C++は、構築されていない元のメモリを割り当てて解放する2つの方法を提供します.
1:allocatorクラス.感知可能なタイプのメモリ割り当てを提供します.このクラスはallocateメンバーを使用してメモリを割り当て、deallocateメンバーを使用してメモリを解放します.
2:標準ライブラリのoperator newとoperator deleteは、サイズが必要な元のタイプ化されていないメモリを割り当てて解放します.
C++は、元のメモリでオブジェクトを構築および取り消す方法も提供します.
1:allocatorクラスはconstructとdestroyという名前のメンバーを定義し、名前が示すように動作します.constructメンバーは、構築されていないメモリでレプリケーションコンストラクション関数を呼び出してオブジェクトを初期化します.destroyメンバーは、オブジェクト上で適切な構造関数を実行します.
2:new式を配置します.メモリを構築していないポインタを指定し、その空間でオブジェクトまたは配列を初期化します.
3:オブジェクトの構造関数を直接呼び出してオブジェクトを取り消すことができます.構造関数を実行しても、オブジェクトが存在するメモリは解放されません.
4:アルゴリズムuninitialized_fillとuninitialized_copyはfillやcopyと似ています.ただし、レプリケーションコンストラクタコンストラクションオブジェクトは、指定されたアドレスで呼び出されます.
現代のC++は一般的にallocatorを使用してメモリを割り当てるべきです.より安全で柔軟です.ただし、オブジェクトを構築するときにallocator::constructメンバーよりもnew式を使用すると柔軟です.newを使用しなければならない場合がいくつかあります.
allocatorクラスは、メモリの割り当てとオブジェクトの構造を分離するテンプレートです.allocatorオブジェクトがメモリを割り当てると、指定したタイプのオブジェクトの適切なサイズの空間が割り当てられます.しかし、割り当てられた空間は構築されていません.allocatorのユーザーはconstructとdestroyを使用してオブジェクトを構築し、解析する必要があります.
allocatoraは、メモリの割り当てやTタイプのオブジェクトの構築に使用されるaというallocatorオブジェクトを定義します.
allocate(n);元の未構築メモリを割り当て、Tタイプのn個のオブジェクトを保存します.
deallocate(p,n);メモリを解放し、タイプT*のポインタpが指すアドレスにn個のオブジェクトを保存し、deallocateを実行する前にdestroyを呼び出すのがユーザーの責任です.
a.contruct(p,t);T*タイプポインタpが指すメモリに新しい要素を作成します.Tタイプのレプリケーションコンストラクタを実行して、そのオブジェクトをtで初期化します.
a.destroy(p);T*タイプポインタpが指すオブジェクトの構造関数を実行します.
uninitialized_fill(b,e,t);反復器bとeによってマークされた範囲のオブジェクトをtのコピーに初期化し、複製構造関数でオブジェクトを構築します.
uninitiated_copy(b,e,b 2)は、反復器bおよびeが示す入力範囲から、反復器b 2から始まる未構築の元のメモリに要素をコピーする.この関数は、値を割り当てるのではなく、宛先に要素を構築します.b 2が示す宛先アドレスは、入力範囲内の要素のコピーを保存するのに十分であると仮定する.
newオペレータを使用すると、operator newという標準ライブラリ関数を呼び出す3つのステップが実際に発生します.指定したタイプのオブジェクトを保存するために、元のタイプ化されていないメモリを十分に割り当てます.2:このタイプのコンストラクション関数を実行し、指定した初期化でオブジェクトを構築します.3:オブジェクトを新しく割り当てて構築するためのポインタを返します.
deleteオペレータを使用する場合、2つのステップが発生します.1:指向するオブジェクトに対して構造関数を実行します.2:operator deleteという標準ライブラリ関数を呼び出して、オブジェクトのメモリを解放します.
new式と標準ライブラリのoperator new関数を区別することに注意してください.
Operator newとoperator deleteには2つの異なるバージョンがあります.各バージョンでは、関連するnew式とdelete式がサポートされています.
void *operator new(size_t);
vodi *operator new [](size_t);
voidoperator delete(void*);
voidoperator delete[](void*);
通常operator newとoperator deleteの設計意図は、newとdelete式で使用されますが、構築されていないメモリを得るために使用できます.これはallocatorのallocateとdeallocate機能と同じです.次のようになります.
T* newelements=alloc.allocate(num);
operator newで次のように置き換えます.
T*newelements=static_cast (operator new[] (num*sizeof(T));
alloc.deallocate(elements,end-elements);
operator deleteで置換:
operator delete[](elements);
注意operator newとoperator deleteとallocateとdeallocateの違いは、void*のポインタで動作することです.一方、allocateおよびdeallocateは、タイプTのポインタ上で動作する.
allocatorはタイプ化されたメモリを割り当て、変換する必要がないため、allocatorはoperator newとoperatorを直接使用するよりもタイプが安全です.
配置new式は、割り当てられた元のメモリでオブジェクトを初期化します.newの他のバージョンと異なる点は、メモリが割り当てられていないことです.形式は次のとおりです.
new(アドレス)タイプ
new(アドレス)タイプ(初期化テーブル)
初期化テーブルは、新しく割り当てられたオブジェクトを構築するときに使用されます.
Alloc.construct(first_free,t);
次の代わりに、位置決めnewを使用します.
new(first_free)T(t);
位置決めnew式を使用すると、allocatorクラスを使用するconstructメンバーよりも柔軟です.位置決めnewは、オブジェクトを初期化するときに任意のコンストラクション関数を使用することができ、construct関数は常にレプリケーションコンストラクション関数を使用するためです.
構造関数は、p->~T()のように明示的に呼び出すことができる.タイプTの構造関数を呼び出し、オブジェクト自体を適切に認識しますが、オブジェクトが占めるメモリは解放されません.注意:operator deleteを呼び出すと、構造関数は実行されず、指定したメモリのみが解放されます.
デフォルトでは、new式は標準ライブラリで定義されたoperator newバージョンでメモリを割り当て、operator newとoperator deleteというカスタムメンバー関数でクラスは独自のタイプに適用されるメモリを管理できます.
コンパイラは、クラスタイプのnewまたはdelete式を表示すると、クラスにoperator newまたはoperator deleteメンバーがいるかどうかを表示します.クラスが独自のoperator newおよびoperator delete関数を定義または継承している場合は、オブジェクトにメモリを割り当てて解放します.そうでない場合は、標準ライブラリのバージョンを呼び出します.
カスタムoperator newとoperator deleteのデフォルトは静的です.明示的にstaticと宣言する必要はありません.コンパイラはデフォルトでstatic関数と見なします.これらの関数は、オブジェクトを構築する前に使用するか、オブジェクトを取り消した後に使用するため、クラスのオブジェクトに依存せずに存在します.
classTest
{
public:
        Test()
        {
 
        }
        staticvoid *operatornew(size_tnum)//num           。
        {
 
 
        }
        staticvoidoperator delete(void *s)//s       。
        {
 
        }
        //  
        staticvoidoperator delete(void *s,size_t n) s       。n s        。
        {
 
        }
};

operator deleteにsize_を指定するとtパラメータの場合、コンパイラが最初のパラメータで指すオブジェクトのバイトサイズでsize_を自動的に初期化します.t形参.これは、クラスが継承階層の一部である場合に必要です.なぜなら.ポインタは、ベースクラスオブジェクトと派生クラスオブジェクトの両方を指すことができます.派生クラスのオブジェクトサイズは一般的にベースクラスオブジェクトよりも大きく、ベースクラスにvirtual構造関数がある場合はoperator deleteに渡されるサイズは、削除されたポインタが指すオブジェクトのダイナミックタイプに応じて変化します.ベースクラスにvirtual構造関数がない場合、ベースクラスポインタによって派生クラスオブジェクトを指す動作を削除することは定義されていません.
クラスタイプの配列を管理するために、メンバーoperator new[]とoperator delete[]を定義することもできます.これらの関数が存在する場合、コンパイラは標準ライブラリのバージョンの代わりに使用されます.
クラスが独自のoperator newとoperator deleteを定義すると、標準ライブラリのoperator newとoperator deleteがマスクされます.ただし、グローバル役割ドメインオペレータによってnewまたはdelete式にグローバルなライブラリ関数を強制することができます.次のようになります.
T*p=::new T;
::delete p;
以下はカスタムVectorクラスです.
#include<iostream>
using namespace std;
#include<algorithm>
#include<stdexcept>
template<typename T>
class Vector
{
public:
	Vector()
	{
		first_free=NULL;
		end=NULL;
		element=NULL;
	}
	void push_back(T t)
	{
		if(first_free==end)
		{
			reallocate();
		}
		//alloc.construct(first_free,t);//      。
		new (first_free)T(t);
		first_free++;
	}
	T &operator[](size_t s)
	{
		if(s>first_free-element)
		{
			throw out_of_range("     !!");
		}
		else
		{
			return element[s];
		}
	}
	const T &operator[](size_t s)const 
	{
		if(s>first_free-element)
		{
			throw out_of_range("     !!");
		}
		else
		{
			return element[s];
		}
	}

private:
	void reallocate()
	{
		int num=first_free-element;
		T*newelement;
		if(num>0)
		{
			newelement=alloc.allocate(2*num);
			//newelement=static_cast<T*>(operator new(2*num*sizeof(T)));//  operator new    。
			uninitialized_copy(element,first_free,newelement);//              。        。
		}
		else
		{
			newelement=alloc.allocate(2);
			//newelement=static_cast<T*>(operator new(2*sizeof(T)));//  operator new    。
			
		}
		for(T*i=element;i!=first_free;i++)
		{
			alloc.destroy(i);//      。
			//i->~T();//        。
		}
		
		alloc.deallocate(element,end-element);//    。
		//operator delete (element);//  operator delete    。
		//     。
		element=newelement;
		first_free=element+num;

		if(num==0)
		{
			end=element+2;
		}
		else
		{
			end=element+2*num;
		}
	}
private:
    allocator<T> alloc;//       static        。
	T*newelement;
	T* first_free;
	T*end;
	T*element;

};




int main(int argc,char**argv)
{
	Vector<int> vi;
	try
	{
	
		for(int i=0;i<20;i++)
		   vi.push_back(i+5);
		for(int i=0;i<20;i++)
		{
			cout<<i+1<<": "<<vi[i]<<endl;
		}
		cout<<vi[28]<<endl;
	}
	catch (exception&e)
	{
		cout<<e.what()<<endl;
	}
	return 0;
}

次に、メモリディスペンサベースクラスを実装します.元のメモリを割り当てて、構築されていないオブジェクトを保存します.新しい要素を作成する場合は、事前に割り当てられたメモリで構築し、要素を解放するときに、メモリを実際にシステムに返すのではなく、事前に割り当てられたオブジェクトのブロックに戻すことができます.このポリシーは、フリーリストを維持するためによく使用されます.この例では、フリーリストを、割り当てられたが構築されていないオブジェクトのチェーンテーブルとして実装します.
フリー・リストの割当てポリシーを使用するクラスが多い可能性があります.したがって、このようなポリシーを必要とするクラスは、このクラスから直接継承することができます.このクラスは任意のタイプのサービスを望むため、クラステンプレートとして定義されます.
書き換えoperator newメンバー関数:newオペレータが呼び出されたときに呼び出される簡単なインタフェースがいくつかあります.フリーリストから構築されていない要素を取り出します.newオペレータの2番目のステップは、newオペレータ構築要素の位置決めを呼び出すからです.Operatordeleteメンバー関数は、削除するポインタが指すオブジェクトをシステムに返すのではなく、フリーリストに追加します.
注:このタイプは、継承階層に含まれるタイプにのみ使用できます.オブジェクトの実際のタイプに応じて、異なるサイズのオブジェクトを割り当てることはできません.フリーリストには、単一サイズのオブジェクトが格納されます.
allocatorを使用してスペースを割り当てます.静的です.フリーカラムヘッダポインタと他の変数はstaticとして宣言されたメンバー変数です.同じタイプのすべてのオブジェクトに対してフリーリストを維持したいからです.コード実装中にリンクエラーが発生し、長い間チェックしていたが、staticのallocatorallocがクラス外で一度も定義されていないことが分かった.これは気をつけてね.
addToFreeListは、ポインタが指す未構築またはプロファイルの空間をフリーリストに追加するために使用される.
nextポインタは、このような派生クラスオブジェクトを指します.フリーリストには派生クラスオブジェクトのチェーンテーブルが格納されているからです.
デバッグ中にoperator newはフォローできますが、operator deleteはフォローできません.しかし、この関数がdeleteオペレータを使用している間に呼び出されたことがテストで証明されました.具体的に何が原因なのかは分からない.どなたか教えてください.
具体的な実装については、コードを参照してください.
#include<iostream>
#include<stdexcept>
using namespace std;

template<typename T>
class MemoryAllocate
{
public:
	MemoryAllocate()
	{

	}
	void *operator new(size_t s)
	{
		if(s!=sizeof(T))
			throw runtime_error("         !");
		if(!freeList)
		{
			T*array=alloc.allocate(num);
			for(size_t i=0;i<num;i++)
			{
				addToFreeList(array+i);
			}
		}
		T*p=freeList;
		freeList=freeList->next;
		return p;
	}
	void operator delete(void*s)
	{
		T*p=static_cast<T*>(s);
		addToFreeList(p);
		cout<<"wobeidiaoyongle "<<endl;//       ,         。         ???20120530 21:22
	}
	virtual ~MemoryAllocate()
	{

	}
private:
	static void addToFreeList(T*t)
	{
		t->next=freeList;
		freeList=t;
	}
private:
	static T*freeList;
	static T*next;
	static allocator<T> alloc;
	static size_t num;

};
  template<typename T>  T*MemoryAllocate<T>::freeList=NULL;
  template<typename T> size_t MemoryAllocate<T>::num=10;
  template<typename T> T*MemoryAllocate<T>::next=NULL;
  template<typename T> allocator<T> MemoryAllocate<T>::alloc;

  class Derived:public MemoryAllocate<Derived>
  {
  public:
	  Derived()
	  {
		  i=0;
	  }
	  int i;
  };
  int main(int argc,char**argv)
  {
	 try
	 {
	 
		 Derived *mai=new Derived;
		 Derived *p=new Derived;


		 delete mai;
		 delete p;
	 }
	 catch (exception& e)
	 {
		 cout<<e.what()<<endl;
	 }
	 return 0;
  }