http://d.hatena.ne.jp/Cryolite/20051021#p1
の問題に対する解答.
まず大前提として,未初期化な領域に配置構文 new を用いてオブジェクトを構築する手法は潜在的な危険が多く,本当にそれが必要な場合以外は用いないという方針が基本であることを確認しておいてください.
まず最初に自動変数として(スタック上に)確保した char の配列に任意のオブジェクトを構築する場合についてです.
class MyClass
{
.....
};
int main()
{
char buf[sizeof(MyClass)];
MyClass *p = static_cast<MyClass *>(static_cast<void *>(buf));
::new (p) MyClass();
..... // #1
p->~MyClass();
}上のコードはアラインメントの問題を抱えています.C++ のオブジェクトはその型毎に構築できるアドレスに制限があり(これをアラインメントの要求といいます),制限を満たさないアドレスにオブジェクトを構築すると未定義の動作を引き起こします.上のコードでは,buf は char の配列であり,そのアドレスは char が構築できるアドレス上に配置されていますが,MyClass のオブジェクトが配置できるアドレスである保証はどこにもありません.そのアドレスに,配置構文 new で MyClass のオブジェクトを構築すると未定義の動作を引き起こします.
Boost には 上記のようなアラインメント周りの問題を解決するパーツが用意されています.TypeTraits ライブラリの boost::alignment_of, boost::aligned_storage です.これを用いることでおおよそのアラインメント周りの問題は解決することができます.
(注意: Boost 1.33.0 のドキュメントは boost::type_with_alignment と boost::aligned_storage の記述がライブラリ著者のタイポで逆になっています.)
また,http://d.hatena.ne.jp/Cryolite/20051021 のコメント欄で郵便はみがきさんが指摘されているように,#1 の部分で例外が送出された際に p に構築されている MyClass のオブジェクトのデストラクタが呼び出されないという問題も生じます.これは RAII,try-catch,ScopeGuard などを用いて対処することができます.ここでは,郵便はみがきさんによる RAII による手法をそのまま採用させてもらうことにします.
上記に挙げた問題を修正したコードが以下です.
#include <new>
#include <boost/utility.hpp>
#include <boost/type_traits/alignment_of.hpp>
#include <boost/type_traits/aligned_storage.hpp>
class MyClass
{
...
};
template<class T>
class scoped_destroy : boost::noncopyable
{
public:
explicit scoped_destroy(T* p) : ptr_(p) {}
~scoped_destroy(){ptr_->~T();}
T& operator*()const{return *ptr_;}
T* operator->()const{return ptr_;}
T* get()const{return ptr_;}
private:
T* ptr_;
};
int main()
{
boost::aligned_storage<
sizeof(MyClass), boost::alignment_of<MyClass>::value
>::type buf;
scoped_destroy<MyClass> p(::new (static_cast<void*>(&buf)) MyClass());
... // p.get(), p->(), *p を用いて構築したオブジェクトを利用する
// ここで明示的なデストラクタの呼び出しは不要.
}次にフリーストア(ヒープ)上に確保した領域上に配置構文 new でオブジェクトを構築する場合です.
class MyClass
{
.....
};
int main()
{
MyClass *p = static_cast<void *>(::new char[sizeof(MyClass)]);
::new (p) MyClass();
..... // #1
p->~MyClass();
delete[] p;
}こちらは元々の出題ミスで色々な問題が複合してしまっています.特に,最後の
delete[] p;
は char の配列として new で確保した領域を MyClass の配列として delete しているため,未定義の動作を引き起こします.
一方で自動変数として確保した領域上にオブジェクトを構築する場合と異なり,こちらの配置構文 new は安全です.というのも,char の配列を確保する new が返すアドレスは,その配列のサイズ以下のあらゆる型のオブジェクトが安全に構築できるアドレスであることが規格上要求されているからです.
もちろん,先と同様 #1 で例外が投入された場合の例外安全性の問題は存在するので,この問題も先と同じ方法で対処します.
#include <new>
#include <boost/utility.hpp>
#include <boost/scoped_array.hpp>
class MyClass
{
...
};
template<class T>
class scoped_destroy : boost::noncopyable
{
public:
explicit scoped_destroy(T* p) : ptr_(p) {}
~scoped_destroy(){ptr_->~T();}
T& operator*()const{return *ptr_;}
T* operator->()const{return ptr_;}
T* get()const{return ptr_;}
private:
T* ptr_;
};
int main()
{
boost::scoped_array<char> buf(new char[sizeof(MyClass)]);
scoped_destroy<MyClass> p(::new(static_cast<void*>(buf.get())) MyClass());
...
}以上のようになります.