ページ 1 / 1
newのオーバーロード
Posted: 2007年9月15日(土) 09:29
by tk-xleader
配列型newで確保したメモリを拡張、収縮したい時に、newのオーバーロードで対処したいのですが、
どうしてもエラーになります。一体どこに問題があるのでしょうか。教えてもらえないでしょうか。
template<class type>
void *operator new[/url](size_t size,type* pointer,size_t beforesize)
{
type *temp;
temp=new type[size];
memcpy(temp,p,beforesize);
delete[/url] p;
return temp;
}
第一引数に変更後のサイズ、第二引数に変更するメモリブロックのポインタ。第三引数に変更前のサイズ。という意図です。
この状態ではメモリを減らす時の事はまだ考慮していません。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 12:00
by Justy
>どうしてもエラーになります
そのエラーはどういうエラーなのでしょうか?
とりあえず見た感じでは 変数pが定義されていないように見えます。
pを pointerにすれば "コンパイル" は通るはずです。
実行時の問題に関しては未完とのことなのでここでは言及はしませんが、
エラーが実行に発生するのであれば、言って下さい。
>配列型newで確保したメモリを拡張、収縮したい時に、newのオーバーロードで対処
std::vectorは使わないのですか?
普通そういう用途で operator new[/url]を定義しようとはしないものなのですが。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 12:26
by tk-xleader
>普通そういう用途で operator new[/url]を定義しようとはしないものなのですが。
今まさにSTLの勉強中で、まだしっかりとは扱えないので、何とかしてnewで今は対処する事にしています。一番いいのはvectorクラスの使用なのですが。
もともとmalloc→reallocという処理の流れを書き換えている作業なので、どうにかして演算子単位で解決したほうがdelete[/url]による開放だとわかりやすくなるのではと思ってオーバーロードしています。
もしかして呼び出し側に問題があるのではないでしょうか。
int main()
{
int *temp;
temp=new int[10];
temp=new int(temp,10)[30];
delete [/url]temp;
return 0;
}
これはサンプルです。書き換えのテストをやっているコードです。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 12:34
by Justy
呼び出し側に問題有りです。
temp=new(temp,10) int[30];
こうです。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 12:38
by tk-xleader
ありがとうございます。何とかコンパイル通りました。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 12:42
by Justy
>malloc→reallocという処理の流れ
なるほど。
それでいろいろ謎がわかりました。
C互換型のオブジェクトが前提だから memcpyだったのですね。
(C++で memcpyは不味いなー、と思っていたので)
Re:newのオーバーロード
Posted: 2007年9月15日(土) 13:42
by tk-xleader
>(C++で memcpyは不味いなー、と思っていたので)
エッ、まずいんですか!?僕は平気でCライブラリ関数乱用してますけど…。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 14:12
by Justy
>まずいんですか!?
Cと互換性を持つオブジェクトなら大丈夫です。
が、問題はCと互換のないオブジェクト・・・例えばデストラクタがある、参照メンバ変数があるとか・・・
の場合、memcpyのようなものでコピーするとビット的には正しくコピーされるかもしれませんが、
オブジェクトとして正当なものになるかは保証はありません。
C++でコピーを行う場合は std::copyのように型に応じてコピーしないと
トラブルの元になるかもしれません。
例えば以下のように。
[color=#d0d0ff" face="monospace]#include <cstring>
struct TestClass
{
TestClass() : i(new int()) {}
TestClass(int v) : i(new int(v)) {}
TestClass(const TestClass& a) : i(new int(*(a.i))) {}
~TestClass() { delete i; }
TestClass& operator =(const TestClass& rhs)
{
if(this != &rhs)
{
delete this->i;
this->i = new int(*rhs.i);
}
return *this;
}
private:
int *i;
};
int main(void)
{
TestClass a(5);
TestClass b;
b = a; // 正当
TestClass *c = new TestClass;
std::memcpy(c, &a, sizeof(TestClass));
delete c; // この deleteの結果・・・
return 0;
} [/color]
最後の delete cの後、aのデストラクタが動いたらどうなることやら。
#追記
このサンプルでは例外は考慮に入れていません。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 14:23
by tk-xleader
組み込み型であれば大丈夫ですよね。まだクラスは普通に静的に実体を作るぐらいの事しかやっていません。std::copyはSTLのアルゴリズム関数でしょうか。
>最後の delete cの後、aのデストラクタが動いたらどうなることやら。
行く果ては2重deleteによるメモリ破壊につながるのでしょうか?
Re:newのオーバーロード
Posted: 2007年9月15日(土) 14:39
by Justy
>組み込み型であれば大丈夫ですよね
なら大丈夫です。
>std::copyはSTLのアルゴリズム
そうです。
あれはちゃんと operator=を使ってコピーするので、実装に問題がなければ
正当なコピーを行います。
>行く果ては2重deleteによるメモリ破壊につながるのでしょうか
ええ、メモリ破壊されるかどうかは環境次第ですが、リーク&二重解放になりますです。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 17:31
by tk-xleader
>あれはちゃんと operator=を使ってコピーするので、実装に問題がなければ
>正当なコピーを行います。
ということは動的メモリ確保や動的インスタンスをメンバに持つ場合、
代入演算子をオーバーロードしなければエラーになるということでしょうか
Re:newのオーバーロード
Posted: 2007年9月15日(土) 18:11
by Justy
>ということは動的メモリ確保や動的インスタンスをメンバに持つ場合、
>代入演算子をオーバーロードしなければエラーになるということでしょうか
エラーというのは [color=#d0d0ff" face="monospace]operator=()[/color]が定義されていないからエラーという意味でしょうか?
そういう意味でしたら組込型にしてもC互換オブジェクトにしても、或いは [color=#d0d0ff" face="monospace]operator=()[/color]を
定義していないクラスにしても [color=#f0c0c0" face="monospace]operator=()は暗黙に定義[/color]されていますから、
(コンパイル)エラーにはなりません。
ただ、実行時での話となると、例えば上の TestClassの例だと [color=#d0d0ff" face="monospace]operator =()[/color]を削除したら、
main関数での [color=#d0d0ff" face="monospace]b = a[/color]の処理以降、aと bのメンバ iは同じポインタ値になるので、どちらかのデストラクタが
動いた段階でもう一方のオブジェクトは "爆弾" と化します。
というわけで、こういうクラスのような場合は[color=#d0d0ff" face="monospace]operator=()[/color]を定義するとか、スマートポインタを使うとか、
或いは根本的にオブジェクトのコピーを禁止するとか、何らかの対処は必要になりますね。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 18:21
by たかぎ
大きなお世話な気もしますが...
> 実装に問題がなければ正当なコピーを行います。
念のため指摘しておくと、
> TestClass& operator =(const TestClass& rhs)
> {
> if(this != &rhs)
> {
> delete this->i;
> this->i = new int(*rhs.i);
> }
> return *this;
> }
は、問題がありますね。
というのも、this->i を delete してから new に失敗すると、このオブジェクトは破綻してしまうからです。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 18:32
by Justy
> 問題がありますね
それはその通りですね。
このサンプルでは例外を考慮に入れていません。
ちょっと一文いれておきます。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 18:44
by tk-xleader
この実装だと、newが失敗すると破綻どころか例外が捕獲できないのでプログラムが以上終了するような気がします。
例外を起こすと問題になる場所でのnewはnothrow形式のnewを使っています。
NULLで帰ってくればifで捕まえてエラー番号を格納するメンバ変数にそれを通知するように実装しています。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 20:38
by たかぎ
> この実装だと、newが失敗すると破綻どころか例外が捕獲できないのでプログラムが以上終了するような気がします。
そんなことはありません。
例外を捕獲するのは、operator= の内部ではなく、それを呼び出した側で行う必要があります。
もちろん、this->i は解放済みのポインタ値が設定されたまま、それ以降は行き詰りますし、おそらくデストラクタで二重解放する羽目になるでしょう。
> 例外を起こすと問題になる場所でのnewはnothrow形式のnewを使っています。
> NULLで帰ってくればifで捕まえてエラー番号を格納するメンバ変数にそれを通知するように実装しています。
この方法を使うと、代入演算子を使うたびにエラー番号の確認が必要になり、非常に面倒です。
そして、おそらくエラー処理を時々忘れることになります。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 20:58
by tk-xleader
>この方法を使うと、代入演算子を使うたびにエラー番号の確認が必要になり、非常に面倒です。
>そして、おそらくエラー処理を時々忘れることになります。
それはまずいですね。ということは例外を投げるほうがコーディングとしてもよくなるということですね。
後、やはりmallocはnewで置き換えるよりも、vectorクラスで置き換えたほうがいいのでしょうか。
Re:newのオーバーロード
Posted: 2007年9月15日(土) 23:11
by たかぎ
> ということは例外を投げるほうがコーディングとしてもよくなるということですね。
何らかの事情で例外が使えないのでない限り、例外を送出する方がよいですね。
> やはりmallocはnewで置き換えるよりも、vectorクラスで置き換えたほうがいいのでしょうか。
はい。これも、何らかの事情で std::vector が使えない状況でない限り、std::vector を使う方がよいと思います。
Re:newのオーバーロード
Posted: 2007年9月16日(日) 02:19
by YuO
Cでmalloc/realloc等を使っていた場合でstd::vectorを使わないのは,std::stringとかstd::wstringを使う場合くらいですかね。
文字列に関しては,\0終端の文字列を代入できたりと,std::vectorよりも便利な事があります。
思いっきり元の話を蒸し返すようですが,operator new[/url]でreallocのかわりをすることは無理ですよ。
operator new[/url]の戻り値とnew[/url]の戻り値は異なることがあるので。
次のコードは,VC++ 2005 Professional環境下で異なる戻り値になる例です。
#include <cstdlib>
#include <cstdio>
class foo
{
private:
int val_;
public:
foo (int val = 0) : val_(val)
{
std::printf("%p : foo.ctor\n", reinterpret_cast<void*>(this));
}
~foo ()
{
std::printf("%p : foo.dtor\n", reinterpret_cast<void*>(this));
}
};
void * operator new[/url] (std::size_t size)
{
void * p = std::malloc(size);
std::printf("%p : op.new[/url] (%u)\n", p, static_cast<unsigned>(size));
return p;
}
void operator delete[/url] (void * ptr)
{
std::printf("%p : op.delete[/url]\n", ptr);
std::free(ptr);
}
int main ()
{
foo * p = new foo[1];
std::printf("%p : new[/url] result\n", reinterpret_cast<void *>(p));
delete[/url] p;
return 0;
}
ちなみに,手元での実行結果は,
003A5BE0 : op.new[/url] (8)
003A5BE4 : foo.ctor
003A5BE4 : new[/url] result
003A5BE4 : foo.dtor
003A5BE0 : op.delete[/url]
でした。
# VC++ 2005 Professionalで確認。
VC++ 2005の場合はデストラクタがなければoperator new[/url]の戻り値とnew[/url]の戻り値が一致するようですが。
# つまり,Cの構造体であれば一致するようです。
ただ,operator new/operator new[/url]が確保した領域に何らかの値を書き込んだ場合に,
コンストラクタの実行やnew/new[/url]から制御が返った時点において書き込まれた値が保持され続けている保証について,
標準を軽く読んだ程度では見つけられませんでした。
Re:newのオーバーロード
Posted: 2007年9月16日(日) 13:57
by tk-xleader
newの中で新たに要求したサイズ分新たに確保して、受け取ったもともとの動的インスタンスをコピーして、受け取ったほうを開放するというやり方では無理でしょうか。
Re:newのオーバーロード
Posted: 2007年9月16日(日) 14:20
by たかぎ
> operator new[/url]でreallocのかわりをすることは無理ですよ。
必ずしも無理ではないですよ。
確かに、operator new[/url] の返却値と、それを用いた new 式の結果がずれる場合がありますが、delete 式によって operator delete[/url] が呼び出される際には、operator new[/url] が返した値が渡されますから。
つまり、使い方を new/delete 式に統一するか、operator new/delete[/url] に統一することができれば、特に問題はないはずです。
もっとも、わざわざこんな管理が面倒なことをやってまで、new でどうにかする必要は全くないと思います。
Re:newのオーバーロード
Posted: 2007年9月16日(日) 14:30
by tk-xleader
やはりこの類の書き換えに使うのはvectorでしょうか。vectorを使えるかどうか検討してみます。