vectorのresizeとassign (C++)

サイズがあらかじめ決まっているvectorの初期化処理でどちらを使うか迷ったのでメモ.

ちなみにVisual Studio 2010で確認.

 

【resize】
cppreference.comの解説

・resize()呼び出し時のvectorサイズ>指定のサイズ

vectorのサイズが指定の要素数になるまで,erase()で削除.

・resize()呼び出し時のvectorサイズ<指定のサイズ

vectorのサイズが指定の要素数になるまで,初期値で埋める.

 

【assign】

cppreference.comの解説

指定された要素数を指定された値で埋めたvectorを作り直す.

・assign()呼び出し時のvectorサイズ>指定のサイズ

指定されたサイズより大きな部分は削除する.

・assign()呼び出し時のvectorサイズ<指定のサイズ

指定されたサイズまでコンテナサイズを広げる。

 

【サンプルコード】


#include <iostream>
#include <vector>
using namespace std;
void dispVec(const vector<int>& vec)
{
cout << "===vecの中身===" << endl;
for(vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
cout << *it << endl;
}
cout << "===ここまで===" << endl;
}
int main(void)
{
vector<int> vec;
for(int i=0; i<3; ++i)
vec.push_back(i);
cout << "vec.size() : " << vec.size() << endl;
//// 表示例
// vec.size() : 3
dispVec(vec);
cout << "\n";
// vectorのサイズよりも大きいサイズ(7)を指定し,初期値(0)で埋める.
vec.resize(7, 0);
cout << "vec.resize(7,0)" << endl;
cout << "vec.size() : " << vec.size() << endl;
//// 表示例
// vec.size() : 7
dispVec(vec);
//// 表示例
// ===vecの中身===
// 0
// 1
// 2
// 0
// 0
// 0
// 0
// ===ここまで===
cout << "\n";
// vectorのサイズよりも小さいサイズ(4)を指定し,はみ出した部分をerase()で削除する. 初期値は利用されない.
vec.resize(4, -3);
cout << "vec.resize(4, -3)" << endl;
cout << "vec.size() : " << vec.size() << endl;
//// 表示例
// vec.size() : 4
dispVec(vec);
//// 表示例
// ===vecの中身===
// 0
// 1
// 2
// 0
// ===ここまで===
cout << "\n";
// vectorのサイズよりも大きいサイズ(7)を指定し,指定した要素数を初期値(5)で埋める.
vec.assign(7, 5);
cout << "vec.assign(7, 5)" << endl;
cout << "vec.size() : " << vec.size() << endl;
//// 表示例
// vec.size() : 7
dispVec(vec);
//// 表示例
// ===vecの中身===
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// ===ここまで===
cout << "\n";
// vectorのサイズよりも小さいサイズ(4)を指定し,指定した要素数を初期値(-3)で埋める.
vec.assign(4, -3);
cout << "vec.assign(4, -3)" << endl;
cout << "vec.size() : " << vec.size() << endl;
//// 表示例
// vec.size() : 7
dispVec(vec);
//// 表示例
// ===vecの中身===
// -3
// -3
// -3
// -3
// ===ここまで===
return 0;
}

参照を利用して自作クラスの受け渡しを高速に行う。

先日の投稿(vectorにスタック変数を格納(C++))でスタック変数をSTLコンテナに格納するようにすれば、deleteの文書かなくてもいいやヒャホーイ

と調子に乗ってコードを修正していたら、パフォーマンスの低下とバグにつながったのでメモ。

今までは

vector<Hoge*> v;
v.push_back(new Hoge());

としていたので、

Hoge* pHoge = v[0];

pHoge->func1()
pHoge->func2()
...

みたいなコードでは、引数、返り値の扱いに伴うコピーが少なかったので、高速に動作していた。

しかし、

vector<Hode> v;
v.push_back(Hoge());

というコードの後で

Hoge hoge = v[0];

hoge.func1()
hoge.func2()

とかすると,いちいちコピーコンストラクタが呼ばれてコピーが作られるため、時間がかかる。

ほんの少しの差だが、このコードが何億回も繰り返し呼ばれるものだからプログラムが終わらなくなった。
さらに、メンバ関数の中でメンバ変数の値を変更したりしていると、終わったところで悲惨な結果が待っている。
なぜなら、hogeはコンテナの中のインスタンスとは全くの別物のインスタンスなので、変更が反映されないから。

どうすればよかったかというと、

const Hoge& hoge = v[0];
もしくは
Hoge& hoge = v[0];

メンバ変数の値を変更するとき以外は上の変数定義を使う。

ポインタと同様の働きをする参照であれば、
受け渡しは高速だし、メンバ変数の変更も反映されるし、
元のコードと同様のはたらきをしてくれる。

逆に言えば,変数に変更を加えたら元のインスタンスも変更されてしまうので,何でもかんでも参照にすればいいというものではないので注意。
変数に変更を加えないことが前提なら必ずconstをつけましょう。

【確認用サンプルコード】


#include <map>
#include <iostream>
class Hoge
{
protected:
int m;
Hoge* p;
public:
Hoge(int x) : m(x), p(this) { }
Hoge(const Hoge& h) : m(h.m), p(this)
{
std::cout << "コピーコンストラクタが呼ばれた" << std::endl;
}
int getM() const { return m; }
void setM(int x) { m = x; }
const Hoge* getAddress() const { return this; }
};
int main()
{
std::cout << "std::map<int, Hoge> m;" << std::endl;
std::map<int, Hoge> m;
{
std::cout <<"Hoge h(3);" << std::endl;
Hoge h(3);
std::cout <<"std::pair<int,Hoge> p(0, h);" << std::endl;
std::pair<int,Hoge> p(0, h);
std::cout << "m.insert(p);" << std::endl;
m.insert(p);
}
std::cout << "\n========参照を使わないパターン=======" << std::endl;
std::cout << "Hoge hoge = m.at(0);" << std::endl;
Hoge hoge = m.at(0);
std::cout << "hoge.setM(0);" << std::endl;
hoge.setM(0);
std::cout << "hoge.getM() : " << hoge.getM() << std::endl;
std::cout << "m.at(0).getM() : " << m.at(0).getM() << std::endl;
std::cout << "hogeのアドレス : " << hoge.getAddress() << std::endl;
std::cout << "m.at(0)のアドレス : " << m.at(0).getAddress() << std::endl;
std::cout << "\n========参照を使うパターン=======" << std::endl;
std::cout <<"Hoge& fuga = m.at(0);" << std::endl;
Hoge& fuga = m.at(0);
std::cout << "fuga.setM(10);" << std::endl;
fuga.setM(10);
std::cout << "fuga.getM() : " << fuga.getM() << std::endl;
std::cout << "m.at(0).getM() : " << m.at(0).getM() << std::endl;
std::cout << "fugaのアドレス : " << fuga.getAddress() << std::endl;
std::cout << "m.at(0)のアドレス : " << m.at(0).getAddress() << std::endl;
return 0;
}
// 実行例
// std::map<int, Hoge> m;
// Hoge h(3);
// std::pair<int,Hoge> p(0, h);
// コピーコンストラクタが呼ばれた
// m.insert(p);
// コピーコンストラクタが呼ばれた
//
// ========参照を使わないパターン=======
// Hoge hoge = m.at(0);
// コピーコンストラクタが呼ばれた
// hoge.setM(0);
// hoge.getM() : 0
// m.at(0).getM() : 3
// hogeのアドレス : 0036FBB0
// m.at(0)のアドレス : 001E4AC0
//
// ========参照を使うパターン=======
// Hoge& fuga = m.at(0);
// fuga.setM(10);
// fuga.getM() : 10
// m.at(0).getM() : 10
// fugaのアドレス : 001E4AC0
// m.at(0)のアドレス : 001E4AC0

view raw

test_ref.cpp

hosted with ❤ by GitHub

vectorにスタック変数を格納(C++)

研究で使っているC++のプログラムを見返しているうちに、非常に初歩的な疑問が湧き、速攻で解決して感動したのでメモ。

3年前に書いたプログラムでは、
STLコンテナ(vectorとかmapとか)に自作クラスのオブジェクトを格納したいとき、

vector<Hoge*> v;
v.push_back(new Hoge());

のような書き方をしていた。おそらくJavaの影響で。
もちろん、メモリ解放用のコードも書いてあった。

しかし、他人からのもらいもののコードの中で

vector<Hode> v;
v.push_back(Hoge());

というコードを見た覚えがあった。

そのときは、気にしなかったが、
今になってふっと

「スタック変数をpush_back()したはずなのに、スコープ抜けた後でもちゃんと動いてるのはなんでだ」

という疑問が湧いた、と同時に、2つ目のコードのほうがdeleteのためのコードが不要になってうれしいことに気付いた。

なぜ問題のコードは動くのか。

「vectorに格納されているインスタンスと渡したインスタンスが別物なんじゃないか?」

という考えに行きついたところで、仮引数と実引数のことを思い出した。

考えてみればpush_back()って関数だよな、と。

初歩的すぎて、なんでも無いことかもしれないが、知識としてだけ頭の中に存在する情報がつながったときは、やはり気持ちがいい。

 

—2014.06.28に追記—

vectorはヒープ領域に変数を格納するので、new/deleteはvectorが勝手にやってくれる。
したがって、格納されているインスタンスのdeleteをプログラマは書かなくても良い。

サンプルコードを新しくした。

【確認用サンプルコード】


#include <vector>
#include <iostream>
class Hoge
{
protected:
int m;
Hoge* p;
public:
Hoge(int x)
:m(x),
p(this)
{
std::cout << "–Hoge(int)–" << std::endl;
std::cout << "コンストラクタの呼び出し" << std::endl;
std::cout << "本オブジェクトのアドレス" << this << std::endl;
std::cout << "—" << std::endl;
}
Hoge(const Hoge& hoge)
:m(hoge.m),
p(this)
{
std::cout << "–Hoge(const Hoge&)–" << std::endl;
std::cout << "コピーコンストラクタの呼び出し" << std::endl;
std::cout << "コピーされるオブジェクトのアドレス" << hoge.p << std::endl;
std::cout << "新たに作成された本オブジェクトのアドレス" << this << std::endl;
std::cout << "—" << std::endl;
}
~Hoge()
{
std::cout << "–~Hoge()–" << std::endl;
std::cout << "デストラクタの呼び出し" << std::endl;
std::cout << "本オブジェクトのアドレス" << this << std::endl;
std::cout << "—" << std::endl;
}
int getM(void) const { return m; }
};
int main()
{
std::vector<Hoge> v;
{
std::cout << "Hoge h(3)でHogeインスタンスを作成" << std::endl;
Hoge h(3);
std::cout << "作成したインスタンスのアドレス" << &h << std::endl;
std::cout << "v.push_back(h)でHogeオブジェクトをvectorに格納" << std::endl;
v.push_back(h);
std::cout << "格納されたインスタンスのアドレス" << &v[0] << std::endl;
std::cout << "ココでスコープを抜ける" << std::endl;
}
std::cout << "格納されたインスタンスのアドレス" << &v[0] << std::endl;
std::cout << "v[0]のmの値 : " << v[0].getM() << std::endl;
// 実行結果(例)
// Hoge h(3)でHogeインスタンスを作成
// コンストラクタの呼び出し
// 本オブジェクトのアドレス0045F8E0
// 作成したインスタンスのアドレス0045F8E0
// v.push_back(h)でHogeオブジェクトをvectorに格納
// コピーコンストラクタの呼び出し
// 本オブジェクトのアドレス009B4FE8
// 格納されたインスタンスのアドレス009B4FE8
// v[0]のmの値 : 3
return 0;
}