Effective C++ 入門
〜 第2章 メモリ管理 〜


□ 5項 □ new と delete のペアは、同じ形式にそろえよう
■ 課題

以下の構文では、99個のstringが正しく開放されない。

削除するポインタが、一つのオブジェクトか配列かdeleteに明示的に通知 しなければならない。

例)
string* stringArray = new string[100];
delete stringArray;


■ 解決

正しくは以下のように、new と delete 、 new [] と delete [] を対応させなければならない

例)
string* stringPtr1 = new string;
string* stringPtr2 = new string[100];
delete stringPtr1;
delete [] stringPtr2;


typedef を用いて型を定義した場合に注意。

例)
typedef string AddressLines[4]
string *pal = new AddresLines;
delete pal;
//不定となる
delete [] pal;
//正解

■ 補足

Arrayを定義するよりは、C++標準ライブラリの vector を使うようにしたほうがよい。vectorは、[] による要素のアクセスもサポートしている。
□ 6項 □ デストラクタでポインタメンバをdeleteし忘れないようにしよう
■ 課題

あたり前の話だが、デストラクタで、ポインタメンバを delete しないとメモリリークが発生する

■ 解決

コンストラクタ、デストラクタを定義するときの注意点

・ コンストラクタで、ポインタを初期化すること。メモリを割り当てない場合は0で初期化する!

・ 代入演算子では、既存のメモリを削除して新しいメモリを割り当てること!

■ 補足

ポインタを初期化するときは、NULLよりも、明示的に0を指定するほうが問題が少ない。詳しくは、『プログラミング言語C++ 第3版 5.1.1 ゼロ』を参照。
□ 7項 □ メモリ不足に備える
■ 課題

メモリが不足したときのエラー処理を独自に定義したい

■ 解決

クライアントがメモリ不足発生時の処理を指定するには、set_new_handler を使う。

以下サンプル。

#include <iostream>
#include <new>
using namespace std;

class SomeClass {
public:
__static void setNewHandler(new_handler p);
__static void* operator new(size_t size);
private:
__int bigArray[300000000];
__static new_handler orgHandler;
};

new_handler SomeClass::orgHandler;
// static な値はデフォルトでゼロに初期化される

void* SomeClass::operator new(size_t size) {
__void* memory;
__cout << "SIZE: " << size << endl;
__try {
____memory = ::operator new(size);
__} catch (std::bad_alloc&) {
____// 定義したハンドラにによる例外処理は終わったので元のハンドラに戻す
____set_new_handler(orgHandler);
____throw;
__}
__// メモリは確保できたので元のハンドラに戻す
__set_new_handler(orgHandler);
__return memory;
}


void SomeClass::setNewHandler(new_handler p) {
__orgHandler = set_new_handler(p);
}


void noMoreMemoryHandler() {
__cerr << "out of memory!!" << endl;
__throw bad_alloc();
}

int main () {
__SomeClass::setNewHandler(noMoreMemoryHandler);
__try {
____new SomeClass;
__} catch (bad_alloc& e) {
____cout << "exception done" << endl;
____return -1;
__}
__return 0;
}

■ 補足

コンパイラオプション no-exception などでメモリ例外の発生を抑止した場合、メモリ不足により new が失敗した場合は、0を返す。詳しくは、『プログラミング言語C++ 第3版 19.4.5 ダイナミックメモリ』を参照。
□ 8項 □ operator new と operator deleteを書くときは規約を守る
■ 課題

operator new も普通の関数と同様に継承される。

たとえば、以下のサンプルでは、派生クラス Derrived は、Base の operator new を使ってメモリを確保する。

したがって Derrived のメンバ int value2_ は未確保のメモリをアクセスすることになる。

#include <iostream>
#include <new>
using namespace std;

class Base {
public:
__Base() : value_(100) {}
__static void* operator new (size_t size);
__static void operator delete (void* rawMemory, size_t size);
__int value_;
};

class Derrived : public Base {
public:
__Derrived() : value2_(200) {} // すでに危険!
__int value2_;
};

Base::Base() : value_(100) {}

void* Base::operator new (size_t size) {
__cout << "Base size:" << sizeof(Base) << " Require alloc size:" << size << endl;
__return malloc(sizeof(Base)); // Baseクラス前提のメモリ確保
}


void Base::operator delete (void* rawMemory, size_t size) {
__cout << "Base size:" << sizeof(Base) << " Require free size:" << size << endl;
__free(rawMemory);
__return;
}

int main() {
__Base* pb = new Base;
__Derrived* pd = new Derrived;
__cout << pb->value_ << endl;
__cout << pd->value_ << ":";
__cout << pd->value2_ << endl; // 危険!
__delete pb;
__delete pd;
}

■ 解決


・ オーバーロードした operator new が自身の想定しているサイズと異なるサイズを要求された場合は、標準の ::operator new に処理を任せること

・ operator delete は、ヌルポインタが渡されることを想定すること。

・ メモリ確保に失敗した場合は、きちんと例外を投げること。

.... (snip) ....
void* Base::operator new (size_t size) {
__if (size != sizeof(Base))
____return ::operater new (size);
__void* p = malloc(sizeof(Base));
____if (p == 0) throw bad_alloc(); // メモリ確保に失敗したらきちんと例外を投げる
__return p;
}
void Base::operator delete (void* rawMemory, size_t size) {
__if (rawMemory == 0) return; // ヌルポインタが渡されることを考慮する
__if (size != sizeof(Base))
____::operator delete (rawMemory);
__else
____free (rawMemory);
__return;
}
.... (snip) ....

■ 補足

・ operaotor new[] (size_t size) を定義したら、operator delete[] (void* rawMemory, size_t size) もきちんと定義すること
・ operator new[] については、生のメモリを割り当てるだけにすること。使用のされ方は無数にあり特定することができないため。決して、sizeof(SomeClass)*個数 を前提にメモリ確保しない。
□ 9項 □ 普通の形式の new を隠蔽しない
■ 課題

operator new に、size_t 以外の引数を定義すると、普通の形式のnew まで隠蔽してしまう。

以下例。

#include <iostream>
#include <new>
using namespace std;

class Base {
public:
__Base() {}
__static void* operator new (size_t size, new_handler p);
};

void* Base::operator new (size_t size, new_handler pHandler) {
__if (pHandler) (*pHandler)();
__return ::operator new (size);
}


void special_handler() {
__cout << "memory allocate!" << endl;
}

int main() {
__Base* b = new (special_handler) Base; // OK
__Base* b2 = new Base; // Compile Error!
__delete b;
__delete b2;
}

■ 解決

標準の引数以外をもつ operator new を定義したら、標準の operator new も再定義しなおすこと。

.... (snip) ....
class Base {
public:
__Base() { }
__static void* operator new(size_t size, new_handler p);
__static void* operator new(size_t size);
};
void* Base::operator new(size_t size, new_handler pHandler) {
__(*pHandler)();
__return ::operator new(size);
}
void* Base::operator new(size_t size) {
__return ::operator new(size);
}

.... (snip) ....


■ 補足

上記例では、new_handler をデフォルト引数としてもよい。

.... (snip) .....
class Base {
public:
__Base() { }
__virtual ~Base() { }
__static void* operator new(size_t size, new_handler p = 0);
};
.... (snip) ....
□ 10項 □ operator new を書くなら、operator delete も書こう!
■ 課題

汎用的な oeprator new は、メモリサイズやパフォーマンスの点で効率が悪い。そのために独自のメモリ管理機構を用いて、operator new を定義して改善する場合がある。

このとき、operator delete の定義を忘れると、独自の定義した operator new で確保したメモリを汎用的な operator delete で削除をしてしまうため悲惨な結果を招く 可能性が高い。

以下例。

(注意:以下のコードを実行しても、おそらく悲惨な結果とはならない。ただし、コードを注意深く追えば危険この上ないことがわかると思う)

#include <iostream>
#include <new>
using namespace std;

class Integer {
public:
__Integer (int v) : value_(v) {}
__void* operator new (size_t size);
__static void Initialize();
__static void Finalize();
__int value_;
private:
__friend int freeCount(Integer* n);
__struct FreeList {
____FreeList* next;
__};
__static FreeList* freeList_;
__static int BLOCK_SIZE;
};

Integer::FreeList* Integer::freeList_;
int Integer::BLOCK_SIZE = 8;

void* Integer::operator new (size_t size) {
__if (size != sizeof(Integer))
____return ::operator new(size);
__if (freeList_ == 0)
____Initialize();
__FreeList* head = freeList_;
__freeList_ = head->next;
__return head;
}


void Integer::Initialize() {
__FreeList* element = static_cast<Integer::FreeList*>(::operator new (sizeof(Integer)));
__freeList_ = element;
__for (int i = 1; i < BLOCK_SIZE; ++i) {
____element->next = static_cast<Integer::FreeList*>(::operator new (sizeof(Integer)));
____element = element->next;
__}
__element->next = 0;
}

void Integer::Finalize() {
__FreeList* nextPtr = freeList_;
__while (nextPtr != 0) {
____freeList_ = freeList_->next;
____delete nextPtr;
____nextPtr = freeList_;
__}
}

int freeCount(Integer* n) {
__int freeCount = 0;
__Integer::FreeList* nextPtr = n->freeList_;
__while (nextPtr != 0) {
____nextPtr = nextPtr->next;
____++freeCount;
__}
__cout <<"FreeList: " << freeCount << endl;
}

int main() {
__Integer::Initialize();
__Integer* n1 = new Integer(1);
__freeCount(n1);
__delete n1; // 危険!これ以降のコードは何が起こるかわからない!
__Integer* n2 = new Integer(2);
__freeCount(n2);
__delete n2;
__Integer::Finalize();
}

■ 解決

operator new を定義したら、operator delete も定義する

これで安心。

.... (snip) ....
class Integer {
public:
__Integer (int v) : value_(v) {}
__void* operator new (size_t size);
__void operator delete (void* rawMemory, size_t size);
__static void Initialize();
__static void Finalize();
__int value_;
private:
__friend int freeCount(Integer* n);
__struct FreeList {
____FreeList* next;
__};
__static FreeList* freeList_;
__static int BLOCK_SIZE;
};
.... (snip) ....
void Integer::operator delete (void* rawMemory, size_t size) {
__if (rawMemory == 0) return;
__if (size != sizeof(Integer)) {
____::operator delete(rawMemory);
____return;
__}
__FreeList* head = static_cast<Integer::FreeList*>(rawMemory);
__head->next = freeList_;
__freeList_ = head;
}
.... (snip) ....

■ 補足

汎用的な operator new が割り当てるメモリは、クラスで定義したサイズ通りの容量ではなく、先頭にメモリブロックのサイズに関するヘッダー領域をつけることがある。したがって、小さなサイズのクラスを定義した場合、クラスそのものの容量よりもヘッダー領域のほうが大きくなる可能性があり、大きなオーバーヘッドとなり得る。詳細は、『EffectiveC++』を参照すること。

Copyright(C) 2005 Yoshinori Oota All rights reserved.

本ホームページのリンクは自由です。複製、転載される場合は、必ず著者までご連絡をください。