スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

【コード】[追記]std::vector を使ってみる

[追記は一番下にしてます]
STL の一つ vector を今回は使ってみました。
実際プログラムに組み込むのはこれが初めてであります。
予め配列の要素数をコンストラクタで渡すことで勝手に領域を確保してくれたり
足りなくなれば勝手に動的確保してくれたりする可変長配列を扱えるコンテナのようだ。

ふむふむ、とても便利そうではないですか。
早速使ってみます。

std::vector Val(30);

これで、要素数30の配列が確保されるそうです・・・便利ね!
要素数が今どれくらいあるのかは size() を使えば取得できます。
Val.size() をすると30と表示されます。

演算子のオーバーロードがありますので、従来通り Val[10] のようなアクセスも可能です。
こいつは便利!!

なるほどなるほど。
では、コンストラクタに要素数を渡せない時、例えばクラス内で
std::vector を保持してる場合なんかは予め配列を確保する関数がないとお話になりませんが
勿論用意されております。


http://www.geocities.jp/ky_webid/cpp/library/002.html
こちらに書いてあるのですが reserve() を使えばいいみたいです。

vector array; // 引数なし
array.reserve( 1000 ); // ここで 1000個分の領域を確保


ほうほう、これでコンストラクタに渡さないバージョンも可能と言う訳ですね!
と言う事は array.size() で1000が返ってくると言う事です。

ここで一回躓きました。
ただ読んでるだけでは得られるものも得られません。
サンプルを作ってみない事には始まらない。

上の方法によって、reserve() を使って配列を確保し、size() で要素数を取得すると
なんと 0 が返ってきます。
1000 個分の領域を確保と書いている割に、確保されておりません。

こっちからするとどういうこっちゃとなる訳です。
もしかすると、1000個分の領域は取ってるけど値が何も入ってないので0が返ってきてるのかも?
と言うまさかの展開も想定し、確保されたつもりでこんな事をしてみます。

array[10] = 100;

Expression: vector subscript out of range
エラー炸裂!!
範囲外にアクセスしてるらしいです!!
え、じゃあ1000個分の領域って何??と疑問に思う訳です。

そこで我らが MSDN 様。
[ MSDN ] http://msdn.microsoft.com/en-us/library/f7yseh4d.aspx

Reserves a minimum length of storage for a vector object,
allocating space if necessary.


要するに、必要に応じて送った引数分の領域を用意してくれるって事なんですけど…
用意して頂けないのはなぜなのでしょうか。
ん~わからん。

しかし、これでは後で可変する場合に対応が出来ない!……と言う事はありません。
ここは resize() を使いましょう。

Val.resize(1000);

これで、Val.size() は1000を返してくれます。
めでたしめでたし!
ちなみに、確保した段階で全ての値は0クリアされているようです。


さすがにこれで終わりだとサンプルもくそもないのでもうちょっと実験したいですよね。
clear() もやっときましょう。
これは、std::list の clear() と同じで、vector 内部で確保した領域全部が消えます。
なので、clear() 後に size() を呼ぶと0が返ってきます。

vector のデストラクタで勝手に削除されるので、
特に理由がない限り明示的に呼ぶ必要はないようですが、
「どこかのタイミングで要素を0にしたい」時なんかに使えますね!


ではでは、要素を追加する事が出来る push_back() もやりたいですね。
引数で渡した値を末尾に追加する事が出来るそうです。

std::vector Val;
Val.push_back(10);

これで Val[0] に10が与えられてます。
では、これはどうでしょう?

std::vector Val(20);
Val.push_back(10);

予め20個の領域を確保して末尾に10を入れる…
末尾に追加なので、Val[0]~Val[19]には0、Val[20] に10が入れられてます。
予め領域を確保したと言っても、push_back すると要素数が増えるって事ですね。
この例だと size() は21を返します。


先頭に追加したい時は std::list 同様、push_front() を使います。
………と言いたい所ですがなんとそんな関数は vector 内に存在しません!( ゚д゚ )
先頭に追加したいなら自前で実装しなさいって事のようです!

更に実験を続けます。


std::vector Val(10);
for(int i=0; i<10; ++i)
  Val[i] = i;

[ ]演算子が使える訳なので、
これで Val[0]~Val[9] は、0~9の値で初期化も出来ます。
このタイミングで Val.resize(5); としたらどうなるでしょうか…

これは、Val[0]~Val[4] にリサイズされ、
Val[0]~Val[4] の値はそのまま残ってます。

消さなければ値は残っているようです!
便利ですね!!

では、ここでもう一度 Val.resize(10); を呼ぶとどうなるでしょうか。
これは、Val[0]~Val[4] の値はそのまま残ってますが、
Val[5]~Val[9] の値は0になってます!

やはり一度削除されると値は0クリアなようです。
当然と言えば当然です。


最後にこんな実験をしたいです・・・・・・new した値を保持したい!

std::vector Val(10);
for(int i=0; i<10; ++i)
  Val[i] = new int;

なんて事はないただの new です。
delete せずにプログラムが終了すると vector のデストラクタが走り
確保された配列は消えてしまいます。
ということは、new int した分も勝手に消えてくれる!!!

…訳ないです。
これはばっちりリークします!
こういうのは責任もって自分で解放しましょう。

こんな感じで std::vector をちょろっと触ってみたのですが中々面白い。
早速テクスチャを std::vector で保持するよう変更しましたが
配列のようにアクセス出来るので結局変数の宣言が変わったくらいで特に変わらなかったですね。

しかし、reserve() が謎いw
あれはいったい何をするものだったのか・・・おじさんには解りませんでした。


[--- 追記 ---]
Justy さんから神のお声を頂き、reserve() が何をするのか理解できました。
情報ありがとうございます><

> 上の方法によって、reserve() を使って配列を確保し、size() で要素数を取得すると
> なんと 0 が返ってきます。
> 1000 個分の領域を確保と書いている割に、確保されておりません。

まずこの部分ですが、そもそも resize() と reserve() のやっている事が違うようです。

resize() は、領域を確保しつつ、オブジェクトも作る!
reserve() は、領域を確保するだけ!
ここに違いがあるようです。

何が違うのかと言うとオブジェクトが生成されるかどうかという違いが生じてます。
そこで、確保されている領域は、capacity() で取得する事が出来ます。

> vector array; // 引数なし
> array.reserve( 1000 ); // ここで 1000個分の領域を確保

ここでは、領域を1000個分確保しただけで、オブジェクトは作られておりません。
なので、size() が0を返してきたという事みたいです。
ここで capacity() を呼ぶと1000が返ってきましたので、やはり領域だけは確保されているようです。

更に、clear() を呼んだとすると、 size() は0を返しますが、
capacity() は1000を返してきます。
オブジェクトを削除はしてくれるようですが、確保した領域分までは消えないようです。

余分な領域を減らすための関数はないようですが、切り詰める方法はありました。

std::vector(Val).swap(Val);

コンストラクタに対象となる vector を渡し、swap 関数で入れ変えると言う方法みたいです。
こうすると、要素自体はそのままで切り詰める事が可能です。
要するに、size=15、capacity=50であっても、
上の方法を用いる事により size() も capacity() も15を返してくれるようになるって事のようです。

サンプルを作ってみたのでこれで何がどうなっているか解りやすいと思います。

#include <iostream>
#include <vector>

int main (void)
{
// 予め10この領域を確保し、オブジェクトを生成する
std::vector<int> Val(10);
// ここでの size:10 capacity:10
std::cout<< "size:"<< Val.size()<< std::endl;
std::cout<< "capacity:"<< Val.capacity()<< std::endl;

Val.reserve(20); // 新たに領域を拡張

// ここでの size:10 capacity:20
std::cout<< "size:"<< Val.size()<< std::endl;
std::cout<< "capacity:"<< Val.capacity()<< std::endl;

std::vector<int>(Val).swap(Val); // 切り詰める

// ここでの size:10 capacity:10
std::cout<< "size:"<< Val.size()<< std::endl;
std::cout<< "capacity:"<< Val.capacity()<< std::endl;

return 0;
}


切り詰めた後、capacity() は10を返してきますので、今の要素数ぴったりの領域が完成です。
切り詰めるのはいいと思いますが、新しい領域を確保する場面が多いのであれば
最初から領域を作っておいた方が後々効率がいいと思います。
しかし、これで拡張し過ぎた分の領域を可変出来る事に成功したので一端めでたしでしょうか…。

改めまして、情報を提供して頂いた Justy さんにお礼申し上げます!!

この記事へのコメント

reserve - Justy - 2012年04月22日 01:07:41

 メモリ領域を確保することと実際にその型のオブジェクトが存在しているかどうかは別なのです。

 reserveは(不足していれば)領域を確保するだけで、resizeは(不足していれば)領域を確保してその領域をオブジェクトとして初期化します。

 ちなみにオブジェクトとして初期化されている要素の数が size()で、領域が確保されている数が capacity()で得られます。

 領域が確保されている以上に要素を追加しようとすると、領域を拡張する為に別の領域をアローケート、要素の内容のコピーを行います(MSVCの実装だと1要素を push_backするときに領域をオーバーするようなら元の領域を 50%大きくた領域を確保するようです)。

 従って std::vector<?> v; として要素を大量に push_backすると、この領域拡張処理が多数発生します。
 そこであらかじめ追加する要素数を reserveしておけば無駄な領域拡張処理を防げる、というわけです。

 ちなみに一度増えた領域は clear()すれば size()は0になりますが、capacityは0にならないので注意。

- DVDM♪管理人♪ - 2012年04月22日 03:32:38

>> Justy さん
お久しぶりです!
コメントありがとうございます><
覗いて頂いていたのに驚きました(笑)

なるほど・・・拡張する処理を省くために reserve() があるのですね。
capacity() ってそういう意味だったのですか;
size() と同じように要素数を返してくると思っていたので
なぜ 0 なのか不思議でした。

領域を確保するだけか、オブジェクトを作るかとは別というのはとても勉強になりました。
ありがとうございます!


clear() 後の capacity() が 0 にならないことを確認しました。
なるほど…。
確保する必要があるなら予め確保しておいた方が絶対いいですね。

resize(), reserve() どちらでも確保して clear() しても、
確保された領域がそのまま保持されているっていうのはとても便利ですね><

情報ありがとうございました!!

- Justy - 2012年04月22日 22:38:31

>覗いて頂いていたのに驚きました
 ちょこちょこ見てますよw

>確保された領域がそのまま保持されているっていうのはとても便利ですね
 とはいえ希に困ることもあるので、領域を開放する方法も調べておいた方がいいかと思います。

- DVDM♪管理人♪ - 2012年04月23日 02:00:44

>>Justy さん
こんな所に足を運んで頂いてありがとうございますw

> とはいえ希に困ることもあるので...
せっかくなので記事に追記させて頂きました。

今回初の vector なので、どういう時に困るのかが良く解らないですが、
「今使っていない領域を、別で使いたくなった時」くらいしか想像できないですね;
そもそもそんなカツカツな状況が生まれるのかどうかも怪しい所ではありますが
そういうお話ではないですよね・・・

- Justy - 2012年04月24日 00:28:01

>せっかくなので記事に追記させて頂きました
 素早い対応ですね。しかも長文!
 ありがとうございます。

>どういう時に困る
 そうですね、例えば A→B→Aと遷移したときにBで使われたメモリは2回目のAの段階で全て解放されているのが望ましいわけですが、寿命が長いところで vectorを定義して使ってしまうと最終的に size()を 0にしてもメモリの使用量チェック引っかかってしまう、とかです。

- DVDM♪管理人♪ - 2012年04月24日 01:34:35

>>Justy さん
いえいえ、プラスの知識も出来ましたので
こちらこそありがとうございます!


今まで使ってた配列の代わりくらいの認識しかありませんでしたが
そういう事にも使えるんですね!

一度確保さえしてしまえば寿命がなくなるまではその領域を保持してくれるようなので
また領域を確保するという処理がいらないため、
勝手に capacity() が0にならないのは個人的にいい事のように感じます。

ただその反面、clear() したんだから領域も0になって欲しい!
と思っている方も居るのかなと思います。

どっちもどっちって感じはしますが、
こうやって領域が可変するのを明示的に処理させるというやり方は
個人的に好きです。
まぁ欲を言うと swap() を使って入れ変える方法で切り詰められるなら
領域を切り詰めてくれる用の関数があってもいいような気はしますけど;

トラックバック

URL :

検索フォーム
プロフィール

DVDM

Author:DVDM
自作ゲームの開発過程ブログ。
赤髪愛なら誰にも負けない。

 
Pixiv バナー


ブロとも申請フォーム
最新記事
カテゴリ
最新コメント
最新トラックバック
RSSリンクの表示
リンク
ブロとも一覧
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。