2008-05-05
以前、
「例外のないC++」ということで、いろいろ考察してみたことがありました。しかし、コンストラクタが失敗したときの解決方法としては、どれもいまいちパッとしません。そこで、改めて、例外処理が使えない環境でコンストラクタが失敗したら、という問題について考えてみることにします。
例外処理が使える環境でも、「例外処理を使いこなせない」、あるいは「例外処理が嫌い」といった事情からか、コンストラクタが失敗した際に、例外を送出する以外の方法を採っているクラスも多く出回っています。それらを見てみると、
- オブジェクト内部に失敗した状態を保持しておく。
- コンストラクタではほとんど何も行わず、別途初期化用のメンバ関数を用意する。
のどちらかのようです。
このうちの1.は以前考察したときに出した結論ですが、状態確認用のメンバ関数を用意しても、それを呼び出すことを強制することは困難ですし、何か行うたびにその都度内部で確認していたのでは、実装が面倒ですし(当然判定処理の漏れも起こりうる)、パフォーマンスも低下します。
2.は初期化の手順が煩雑になり、C++の思想から外れているように思います。
というわけで、別の方法が必要になりそうです。もし、動的なオブジェクト生成が許されるのであれば、
A* create_A()
{
A* p = new A;
if (p->fail())
return 0;
return p;
}
のような方法もありでしょう。しかし、C++の大きな利点のひとつは、オブジェクトをスタック上に生成できることであり、その利点を失うのはあまりにも代償が大きすぎます。
というわけで、次のような方法はどうでしょうか?
template <class T>
class builder
{
T object_;
public:
T* get()
{
if (object_.fail())
return 0;
return &object_;
}
};
int main()
{
builder<A> b;
if (A* pa = b.get())
{
...
}
}
この方法であれば、getメンバ関数を呼び出さない限り、Aのインスタンスにアクセスできません。また、Aの生成に失敗していれば空ポインタが返ってきますので、誤ってこれを使おうとした場合、比較的早い段階でプログラムがクラッシュします。また、Aのインスタンスはスタック上に生成することができます。
もっと安全性を強化するには、getメンバ関数は生のポインタを返すのではなく、空ポインタに対して->演算子等を使おうとするとassertで落ちるようにしたスマートポインタを使うのもひとつの手です。
template <class T>
class assert_ptr
{
T* ptr_;
public:
explicit assert_ptr(T* ptr) : ptr_(ptr) {}
T* operator->() const
{
assert(ptr_ != 0);
return ptr_;
}
};
難がないわけではありませんが、それなりに効果的な方法ではないかと考えています。
Comment