第49章 破壊と創造

 前回の new オーバーロードを利用すると、面白いことができます。また、実はVC++の環境でも new が失敗したときに例外を投げるようにできます。今回はそういうお話です。


 では、今回の要点です。


 では、いってみましょう。


 突然ですが、コンストラクタやデストラクタを直接呼んでみたことのある人はいるでしょうか? やったことのある人も、やっとことのない人も、とりあえず次のプログラムをコンパイルしてみましょう。

プログラム1
// New5.cpp
#include <iostream.h>

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

int main()
{
    CTest test;
    test.~CTest();
    test.CTest();
    return 0;
}

 これはもちろんコンパイルエラーが発生します。しかし、よくみるとコンパイルエラーは1つです。

New5.cpp(15) : error C2274: 'function-style cast' : '.' 演算子の右側として不正です。

 15行目は上のプログラムでいえば test.CTest(); のある行です。つまり、デストラクタは直接呼べるのです。

 しかし、このまま test.CTest(); を消すと test の寿命が尽きたところでもう一度デストラクタが呼ばれ、普通はおかしなことになります。そこで、是非ともコンストラクタを呼びたいわけです。

 これには、前回の new オーバーロードが利用できます。new の仕様は、operator new の返したアドレスを使ってコンストラクタを呼ぶというものでした。つまり、ただアドレスを返すだけの new を作ってしまえばいいわけです。

プログラム2
// New6.cpp
#include <iostream.h>

inline void* operator new(size_t, void* ptr)
{
    return ptr;
}

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

int main()
{
    CTest test;
    test.~CTest();

    new(&test) CTest;
    return 0;
}
実行結果
CTest
~CTest
CTest
~CTest

 コンパイルすると警告が出るかもしれませんが、関係がないので無視して構いません。こうして無事にコンストラクタを呼ぶことができました。new[] にすれば、配列にも対応できます。

 引数付きの new のことを placement(プレイスメント)形式の new といいますが、このコンストラクタを呼ぶだけの new のこともただ単に placement new と呼びます。

 「こんなことをすることがあるのか」という疑問もあるかも知れませんが、Windows 独特のメモリ確保(LocalAlloc や GlobalAlloc などの関数)を使った場合などにはコンストラクタが呼べないので、placement new でコンストラクタを呼ぶ必要があります。不必要と切り捨てることはできません。

 実は、placement new は new というヘッダの中に定義されています。new をインクルードすれば自動的に placement new が使えるわけです。


 この new ヘッダファイルですが、new が失敗したときに投げる例外型 std::bad_alloc の定義も入っています。std は名前空間(第2部第50章参照)です(std 名前空間については第4部で詳しく話します)。

 しかし、残念ながら(腹立たしいことに)VC++はデフォルトで例外を投げません。std::bad_alloc を定義しているにもかかわらず投げないのは妙なことです。

 そこで、VC++では _set_new_handler という関数を使って new が失敗したときに呼ばれる関数を設定できるので、それを使って対処します。これは new.h ヘッダファイルに宣言されています。ダサい仕様ですが、今のところ我慢です。

プログラム3
// New6.cpp
#include <iostream.h>
#include <new.h>
#include <new>

using namespace std;  // using しておきます

int ThrowBadAlloc(size_t)
{
    throw bad_alloc();
    return 0;
}

int main()
{
    _set_new_handler(ThrowBadAlloc);

    try
    {
        int* p = new int[0xFFFFFFF];
        delete [] p;
    }
    catch(bad_alloc e)
    {
        // what メンバ関数でエラー文字列を取得できます
        cout << e.what() << endl;
        return 1;
    }
    return 0;
}
実行結果例
bad allocation

 VC++以外の環境ではどうかというと、gcc は最近のバージョンでは問題なく bad_alloc 例外を返します。std::set_new_handler 関数も用意されていますが、普通は使う必要ありません。

 また、new に std::nothrow という引数を渡せば NULL を返すようになります。例えば、

int* p = new(std::nothrow) int[0xFFFFFFF];

こんな感じです。こうすれば new が失敗したときに NULL を返すようになります。VC++もそれらしき記述がヘッダファイルに見えますが、何故か機能しません。早く完全対応してほしいものです。


 では、今回の要点です。


 それでは、また。


第48章 同姓同名4 | 第50章 型チェック

Last update was done on 2001.5.6

この講座の著作権はロベールが保有しています