GRAMさん、ISLeさん、loweさん返信ありがとうございます。
GRAM さんが書きました:
ややこしいという話ですが、自分には到底そうは思えません。
このコンベンションがややこしくないという理由はいくらでもあります。
①Hogeクラスに何も手を加えない。(空のコンストラクタの制約もない)
②実行結果からわかる通り、引数ありのコンストラクタ呼び出しは1回ですむ。(後述のように、コピーコンストラクタは呼ばれてしまう)
またデフォルトコンストラクタは1度も呼ばれていない。そう。たったの1度も。
③メモリのことを考える必要から解放されている。(reserveで要素数をいれるということを除けば。しかしこれすらも、効率を気にしなければ省ける)
④プレースメントnewという、テクニカルなことをする必要がない。
(ヒープから確保するならなおさらテクニカル。解法や、デストラクタの明示的呼び出し等。またアライメントのことを考える必要も全くない)
⑤この方法ではデストラクタのことを考える必要もない。vectorが破棄されるときに自動で呼ばれる。
⑥そもそも、この方法のデメリットというのがあまり思いつかない。
⑦ただし、あえてデメリットを挙げるとしたら、コピーコンストラクタが必要になる。それを作りたくないなら確かにplacement_newを使うしかないのかもしれない
⑧そもそもこのコードを見て、複雑だと感じるでしょうか?自分はふつうに読めるコードだと思います。
GRAMさんの書いたコードをみるとそこまで複雑ではないですね。
loweさんのコードで複雑に感じたのはメンバに代入する部分も
書いてもらったためイテレータなども組合わさっていたためですね。
vectorのポインタ イテレータ テンプレートと見慣れないものが
たくさんあったためなので慣れればそうでもないかなと思えました。
placement new や newのほうがテクニカルなのですか。
vectorのほうが高度でテクニカルだと思ってました。
コピーコンストラクタはクラスをコピーするときに使うので、
デフォルト以外を使うなら他の方法でも必要な気がします。
教えていただいたコードにも書かれていなく普通にコピーするだけのときは
書かなくてもいいようですし特にデメリットというほどでもないですね。
GRAM さんが書きました:
さて、std::vectorですが、 確保しているメモリが連続していなくてはならなくて
しかも、償却定数時間で後ろに要素を付け加えていくことができるわけですから、
考えうる限りでは、あらかじめ確保しているメモリよりも多くの要素を追加しようとすると、メモリの再確保が行われると考えられます。
これも自分の知る限りはですが、償却定数時間にしようとすると、少なくとも現在の要素のx倍(xはなんでもいいが2が使われるのかな?)
のメモリを確保しなくてはなりません。(そうすることで、要素を無限に加えていったときにメモリの再確保の回数がいずれなくなる。)
となると。です。reserveしなくてもいいのですが、reserveした方が、圧倒的に効率がよくなることがあります。
たとえばこの例だとしても、「仮に」最初にメモリが全く確保されていなければ(これはわからない)
push_backのたびに、1回目(メモリ1確保) 2回目(メモリ2確保) 3回目(メモリ4確保) 4回目( メモリ確保なし) 5回目(メモリ8確保)
・・・となって、4回もメモリの再確保が行われていたとしても文句は言えないわけです。
さて、もっと大きなサイズならどうなるのでしょうね?
・・・という理由でreserveします。
reserveがあったほうがいい理由がわかりました。
丁寧な説明ありがとうございます。
GRAM さんが書きました:
最後にoperator.か->かという話ですが、見ての通りです。vectorは場合によってはoperator[]でアクセスしたほうがいい時もあります。
(ループ途中で要素が追加されるときなど)
iteratorしか使えないわけでもないので、そこらへんはご自由に。(自分は結構使い分ける人ですが)
operatorがiteratorのようなものかと思って調べましたが普通に演算子のことなんですね。
例のプログラムを見たところ[]のほうがわかりやすいなと思えました。
ISLe さんが書きました:プレースメントnewを使うときに変な誤解をするといけないと思って指摘しただけなのに、横道が伸びてしまって申し訳ないです。
いえ、使ったことのないものだったので詳しい説明や注意などを聞けて勉強になります。
質問してばかりですが毎回丁寧に回答していただきありがたいです。
ISLe さんが書きました:
一時変数の配列aを宣言することで(これ以前に)確保されたメモリを引数の無いコンストラクタで初期化します。
つまり宣言しただけでクラスオブジェクトが構築されます。
プレースメントnewはコンストラクタを呼び出して、指定したアドレスを先頭としたメモリを初期化するだけです。
そして初期化によって上書きしてしまうわけですから、既にそこにあったオブジェクトは破壊されます。
一時変数の配列aはfunction関数の終わりでライフサイクルを抜けます。
それと同時にデストラクタが呼び出されクラスオブジェクトが解体されます。
そのあとメモリも解放されます。
ということはfunction関数を抜けた時点でplacement newで配置したものはなくなるので
2回目以降に1回目のfunction関数で変更した値は残っていないんですね。
なら、静的というのはどういうことですか?静的というとプログラムの最後までメモリが確保され
残っているものだと思っていたのですが、これが間違いですか?
ISLe さんが書きました:
破壊されたオブジェクトがライフサイクルを終えて解体されるという状況を、わたしはバグだと思います。
placement newを使う以上しょうがないことだと思うのですが
placement new自体を使わないほうがいいということでしょうか?
lowe さんが書きました:
ああ、
コード:
#include <vector>
int main(){
using namespace std;
int x, y;
vector<A> Vector; // A型の動的配列
Vector.push_back( A(1) );
Vector.push_back( A(2) );
// ~処理~
Vector[x-1].abc = 0;
y = Vector[x-1].cint;
return 0;
}
みたいにすればできると思いますが・・・ただこれだと動的な確保が面倒になりませんかね?この場合のAがどういう使い方をされるかによりますが
GRAMさんがほとんど説明されてますので特に説明はいらないと思いますが、vectorを使えばplacement newよりいいことはたくさんあります。(placement newは実際はどちらかっていうとメモリプールとかに使うテクニックなんで)
vectorはpush_backで動的に要素を追加していけるのではないのですか?
placement newはメモリプールに使うものだったのですか。
lowe さんが書きました:
boostライブラリにptr_vectorなんてものがあります。
かなり便利なんで私も多用しちゃってるんですが、、、
それを使うとこんな感じになります。
コード:
#include <boost/ptr_container/ptr_vector.hpp>
int main(){
using namespace std;
int x, y;
boost::ptr_vector<A> Vector; // A型の「ポインタの」動的配列
Vector.push_back( new A(1) ); // ポインタを入れる
Vector.push_back( new A(2) );
// ~処理~
Vector[x-1].abc = 0; // .演算子でアクセスできる
y = Vector[x-1].cint;
return 0;
}
これを使えば.演算子でアクセスできますし(イテレータでもoperator[]を使った場合でも)、要素を消すときにいちいちdeleteを呼ぶ必要もなく、newでつっこめるんで動的確保も楽です。
・・・まぁboostを導入しなくちゃいけなかったり若干注意しなくてはならない点もあったりするんでvectorを知らないならオススメはしませんが、参考までに。
vectorは名前は聞いたことがあったのですがboostは全く聞いたことがありませんでした。
調べてみたらVC++とは別にインストールがいるようですね。
便利そうですがC++ですらまだ知らない部分が多く回答いただくたびに知らない用語を
いろいろ調べているレベルなのでboostの導入は今回しないでおきます。
最初の頃beatleさんが代案としてだしてくれたmapの方法にあったboostというものも
これなのですね。みたことがないincludeだなと思っただけで流してしまいましたが。。
少し話がずれますが
GRAMさんが書いてくれたコード
GRAM さんが書きました:
コード:
#include <iostream>
#include <vector>
using namespace std; //楽します。スミマセンw
class Hoge
{
public:
Hoge( int n)
: num(n)
{
cout << "コンストラクタが呼ばれました。値は" << num << "です" << endl;
}
void ShowNum() const
{
cout << num << endl;
}
private:
const int num;
};
int main()
{
vector< Hoge > arrayHoge; //Hogeクラスを格納するvector
arrayHoge.reserve(5); //vectorに確保されているメモリを最低でも5以上にする
arrayHoge.push_back( Hoge(1) ); //あとは入れていくだけ
arrayHoge.push_back( Hoge(2) );
arrayHoge.push_back( Hoge(3) );
arrayHoge.push_back( Hoge(4) );
arrayHoge.push_back( Hoge(5) );
cout << "-------------------" << endl;
for( int i = 0; i < 5; ++i )
{
arrayHoge[i].ShowNum();
}
}
これの実行結果をみようと実行しようとしたのですが
最初見たときVC++を入れてないPCで見たので
Cygwinで実行しようとしました。
すると、エラーが出てコンパイルできませんでした。
いろいろ変えてみたりして
・const int num;をint num;にしたとき
・vector使わずにHoge h(1);としたとき
はコンパイルが通りました。
通ったはいいがダメだった理由はわかりません。
このままだとコンパイルが通らない理由はなんなのでしょう?
VC++でしか使えない書き方ではないと思うのですけど・・・
エラーメッセージは
~/vector.tcc:238: error: non-static const member `const int Hoge::num', can't use default assignment operator
です。
VC++2010を入れている方で後で試したらそのままのコピペで
動作しました。