ページ 11

placement new について

Posted: 2010年10月08日(金) 10:55
by 山崎
おはようございます。
いつもお世話になっております、山崎と申します。
今回は、placement new についていくつかお伺いに参りました。

OSはXP、エディタはVisual Studio2010を使っております。

前々から興味があったplacement newについて、現在いろいろ調べております。
普通のnewよりどれくらい高速に動作するのかを調べようとプログラムを作っていたのですが、
その前にいろいろわからないことが出てきましたので、皆様のお力をお借りしたいと思います。
なお、placement new については主に以下のサイトを参考に学習しております。
http://www.geocities.jp/ky_webid/cpp/language/036.html

1.
メモリプールとして前もって確保しておくメモリ領域の型は、何でもいいのでしょうか?
char型として確保した領域にint型の値を割り当てたり、
あるいは自作したクラスの型でメモリを確保し、その領域にさらに違うクラスの値を割り当てるなど…。
そういうことは可能なのでしょうか。または、そういった使い方は安全なのでしょうか。

2.
メモリプールの、既に値を割り当てて使用中の領域に対して、誤ってさらに値を割り当てようとした時、
それは実行時エラーにならないのでしょうか?
仮にそれがエラーにならないとしたら、
「メモリプールのどこが使用中で、どこが空いているか」という情報の管理は、
プログラマがしなければいけないのでしょうか。

3.
メモリプールの中のどの位置に値を割り当てるかは選ぶことができるのでしょうか?

4.
デストラクタを明示的に呼び出した後の領域は、不定な値にならないのでしょうか?
仮に、デストラクタで後処理した後の領域に誤ってアクセスしてしまった場合、
デストラクタが呼び出された後の未使用領域ということに気づかずそのままプログラムが走り続けてしまうのでしょうか。

5.
普通のnewに比べて、placement newはどれくらい高速なのでしょうか。
自動変数を利用するくらいに高速なのでしょうか。

似たような意味の質問もあるとは思いますが、どうかお教えいただければ幸いです。

参考に、placement newをとりあえず試しに使ってみた時のコードを貼り付けておきます。
#include <stdio.h>
#include <stdlib.h>
#include <new>

class TestClass
{
public:
    int TestInt;
    double TestDouble;

    TestClass() {printf("called constructor\n");}
    ~TestClass() {printf("called destructor\n");}
    void PrintMember() {printf("Int=%d Double=%lf \n",TestInt,TestDouble);}

    void SetMember()    //テキトーな値を入れてみる
    {
        TestInt=rand();
        TestDouble=(double)rand()/10;
    }
};

int main()
{
    char* Pool=new char[sizeof(TestClass)*5];    //char型でメモリプールを作る

    TestClass* TestSpace[5];
    for(int i=0;i<5;i++)
    {
        TestSpace=new(Pool) TestClass();
        TestSpace->SetMember();
    }

    TestSpace[0]->~TestClass();

    return 0;
}

Re:placement new について

Posted: 2010年10月08日(金) 12:11
by たかぎ
> 1.
> メモリプールとして前もって確保しておくメモリ領域の型は、何でもいいのでしょうか?
> char型として確保した領域にint型の値を割り当てたり、
> あるいは自作したクラスの型でメモリを確保し、その領域にさらに違うクラスの値を割り当てるなど…。
> そういうことは可能なのでしょうか。または、そういった使い方は安全なのでしょうか。

一般的には不可能ですし、安全でもありません。
x86であれば、たまたま動くとは思いますが...

> 2.
> メモリプールの、既に値を割り当てて使用中の領域に対して、誤ってさらに値を割り当てようとした時、
> それは実行時エラーにならないのでしょうか?
> 仮にそれがエラーにならないとしたら、
> 「メモリプールのどこが使用中で、どこが空いているか」という情報の管理は、
> プログラマがしなければいけないのでしょうか。

メモリプールというのが何を指しているのか分かりませんので、それ次第でしょう。

> 3.
> メモリプールの中のどの位置に値を割り当てるかは選ぶことができるのでしょうか?

これも、その「メモリプール」次第です。

> 4.
> デストラクタを明示的に呼び出した後の領域は、不定な値にならないのでしょうか?
> 仮に、デストラクタで後処理した後の領域に誤ってアクセスしてしまった場合、
> デストラクタが呼び出された後の未使用領域ということに気づかずそのままプログラムが走り続けてしまうのでしょうか。

不定だという認識でよいと思います。
誤ってアクセスした場合でも検出方法はありませんので、ポインタにNULLを入れておくなどしたほうが無難です。

> 5.
> 普通のnewに比べて、placement newはどれくらい高速なのでしょうか。
> 自動変数を利用するくらいに高速なのでしょうか。

newに渡すメモリブロックを割り付ける時間によります。

Re:placement new について

Posted: 2010年10月08日(金) 19:58
by 山崎
たかぎさん
ご返信、まことにありがとうございます。
まだまだ学習中の身ですので拙い質問だったかもしれません、
御親切にお答えくださりありがとうございました。

Re:placement new について

Posted: 2010年10月09日(土) 01:55
by ISLe
> TestSpace=new(Pool) TestClass();

上記のコードだとTestSpace[0]~TestSpace[4]に入るアドレスはすべてPoolと同じアドレスになるのですけど。

Re:placement new について

Posted: 2010年10月09日(土) 09:23
by 山崎
ISLeさん
ご返信ありがとうございます。
おっしゃるとおりで、上のソースコードでは全て同じ場所に値が代入されてしまっていて、
コードを書いた人が期待したであろう結果が得られていません。
そんな事態が起こったので、こちらで質問をするにいたったというわけなのです。

ただ、質問の趣旨は「期待した結果が得られないのですが、どこをどう直せばいいのですか」ではなく、
3番目に質問させてもらったように「値を割り当てる場所は指定できるのかどうか」と少し一般的な
感じの質問にさせていただきました。

Re:placement new について

Posted: 2010年10月09日(土) 10:38
by めるぽん
placement new は必要無いのであれば使わないに限りますが、どうしても必要だということになれば、最低でもアライメントの知識は入れておくべきでしょう。
http://d.hatena.ne.jp/Cryolite/20051102#p1
がよくまとめられています。

>あるいは自作したクラスの型でメモリを確保し、その領域にさらに違うクラスの値を割り当てるなど…。
>そういうことは可能なのでしょうか。または、そういった使い方は安全なのでしょうか。
仕様上では、たとえば、
void* p; // 何かの領域が割り当てられているとする
Hoge* q = new(p) Hoge();
Fuga* r = new(p) Fuga();
のように、同じ領域に別のオブジェクトを構築することは未定義です。なぜかというと、q のライフタイムが終了する前に p の領域に別のオブジェクトを構築しているからです。
void* p;
Hoge* q = new(p) Hoge();
q->~Hoge();
Fuga* r = new(p) Fuga();
とするのは、デストラクタ呼び出しによって q のライフタイムが終了しているため、この領域を使っても構いません。

デストラクタを呼ばずに構築してもいいケースはいくつか挙げられますが、一般的にはこのように明示的にデストラクタを呼んでから再構築するべきです。

Re:placement new について

Posted: 2010年10月09日(土) 11:42
by ookami
私も基本的には、明確なメリットがなければ「やめといたほうが」と思いますが、

> 3番目に質問させてもらったように「値を割り当てる場所は指定できるのかどうか」

ということなら、
TestSpace=new(Pool) TestClass();

TestSpace=new(Pool+sizeof(TestClass)*i) TestClass();
でいけるんじゃないですかね。

Re:placement new について

Posted: 2010年10月09日(土) 18:26
by ISLe
> ただ、質問の趣旨は「期待した結果が得られないのですが、どこをどう直せばいいのですか」ではなく、
> 3番目に質問させてもらったように「値を割り当てる場所は指定できるのかどうか」と少し一般的な
> 感じの質問にさせていただきました。

指定したアドレスから始まる領域にオブジェクト割り付ける機能でしかないので、どんなアドレス値を渡すか、いつどこに領域を確保するか、はすべてプログラマの責任で行う必要があります。

自前で管理するメモリプールを使いたいならnewとdeleteをオーバーライドすればdeleteもふつうに呼び出せて差し替えとか楽です。
落ち物パズルゲームみたいにオブジェクトの最大数が決まっているときに固定で確保したメモリを使い回すのをやったことあります。

あとVisual C++の有償版にあるDEBUG_NEWマクロと同時に使えませんね。
newのオーバーライドでデバッグしたいところに合わせてメモリ確保の方法を切り替えると効率が良いです。

Re:placement new について

Posted: 2010年10月09日(土) 21:43
by 山崎
ぬるぽんさん、ookamiさん、ISLeさん、
ご返信まことにありがとうございます。
皆様に提示いただいたサンプルコードやサイト、ノウハウのおかげで
なんとかplacement newを使っていくことができそうです。
御親切にお教えいただき、誠にありがとうございます。

ぬるぽんさん
紹介頂いたサイトを拝見しました。
placement newに関するノウハウがたくさんつまっているようでした、
この後じっくり読ませて頂きますね。
お気に入りに登録して、ことあるごとに参考にさせて頂きます。

ookamiさん
なるほど、newのカッコの中身でアドレスを指定すればいいのですね・・・。
参考にしてプログラムを組んでみようと思います。
ありがとうございます。

ISLeさん
やはり、プログラマが管理する必要があるのですね・・・。
やってみようと思います。
アドバイス誠にありがとうございました。