web-dev-qa-db-ja.com

オブジェクトのC ++ベクトルとオブジェクトへのポインターのベクトル

私はopenFrameworksを使用してアプリケーションを作成していますが、私の質問はoFだけに固有のものではありません。むしろ、一般的なC++ベクトルに関する一般的な質問です。

別のクラスの複数のインスタンスを含むクラスを作成したかったが、それらのオブジェクトと対話するための直感的なインターフェイスも提供した。内部的には、私のクラスはクラスのベクターを使用していましたが、vector.at()を使用してオブジェクトを操作しようとすると、プログラムはコンパイルされますが正しく動作しません(私の場合、ビデオは表示されません)。

// instantiate object dynamically, do something, then append to vector
vector<ofVideoPlayer> videos;
ofVideoPlayer *video = new ofVideoPlayer;
video->loadMovie(filename);
videos.Push_back(*video);

// access object in vector and do something; compiles but does not work properly
// without going into specific openFrameworks details, the problem was that the video would
// not draw to screen
videos.at(0)->draw();

どこかに、それらのオブジェクト自体のベクトルではなく、そのクラスのオブジェクトへのポインターのベクトルを作成することが提案されました。私はこれを実装しましたが、実に魅力のように機能しました。

vector<ofVideoPlayer*> videos;
ofVideoPlayer * video = new ofVideoPlayer;
video->loadMovie(filename);
videos.Push_back(video);
// now dereference pointer to object and call draw
videos.at(0)->draw();

オブジェクトにメモリを動的に割り当てていました。つまり、ofVideoPlayer = new ofVideoPlayer;

私の質問は簡単です。なぜポインターのベクトルを使用したのか、そしてオブジェクトのベクトルとオブジェクトへのポインターのベクトルをいつ作成するのですか?

35
Alan

私の質問は簡単です。なぜポインターのベクトルを使用したのか、そしていつオブジェクトのベクトルとそれらのオブジェクトへのポインターのベクトルを作成するのですか?

std::vectorは、現在のサイズよりも多くの要素をプッシュしようとすると、newで割り当てられ、再割り当てされた生の配列に似ています。

したがって、Aポインターが含まれている場合は、A*の配列を操作しているようです。サイズを変更する必要がある場合(要素が現在の容量に既に満たされている間にPush_back())、別のA*配列を作成し、前のベクトルからA*の配列にコピーします。

Aオブジェクトが含まれている場合、Aの配列を操作しているようです。したがって、自動再割り当てが発生する場合、Aはデフォルトで構築可能です。この場合、Aオブジェクト全体も別の配列にコピーされます。

違いを見ます? std::vector<A>Aオブジェクトは、内部配列のサイズ変更を必要とする操作を行うと、アドレスを変更できます。それが、std::vectorにオブジェクトを含める際のほとんどの問題の原因です。

このような問題を発生させずにstd::vectorを使用する方法は、最初から十分な大きさの配列を割り当てることです。 ここでのキーワードは「capacity」です。std::vector容量は、realそれが置かれるメモリバッファのサイズです。オブジェクト。したがって、容量を設定するには、2つの選択肢があります。

1)構築時にstd::vectorのサイズを設定して、最初からすべてのオブジェクトを最大オブジェクト数で構築します。これにより、各オブジェクトのコンストラクターが呼び出されます。

2)std::vectorが構築されると(ただし何も含まれません)、そのreserve()関数を使用:ベクターは十分な大きさのバッファーを割り当てます(最大サイズを指定します)ベクター)。 ベクトルは容量を設定します。このベクトル内のPush_back()オブジェクトまたはresize()呼び出しで指定したサイズの制限の下でreserve()を使用すると、内部バッファーが再割り当てされず、オブジェクトはメモリ内の場所を変更せず、それらのオブジェクトへのポインタを常に有効にします(容量の変更が発生しないことを確認するいくつかのアサーションは優れたプラクティスです)。

19
Klaim

C++のベクターについて知っておくべきことは、ベクターに入力できるように、オブジェクトのクラスのコピー演算子を使用する必要があるということです。これらのオブジェクトにデストラクタが呼び出されたときに自動的に割り当てが解除されたメモリ割り当てがある場合、問題を説明できる可能性があります。オブジェクトがベクトルにコピーされてから破壊されました。

オブジェクトクラスに、割り当てられたバッファを指すポインタがある場合、このオブジェクトのコピーは同じバッファを指します(デフォルトのコピー演算子を使用する場合)。デストラクタがバッファの割り当てを解除すると、コピーデストラクタが呼び出されたときに元のバッファの割り当てが解除されるため、データは使用できなくなります。

ポインタを使用する場合、この問題は発生しません。これは、new/destroyを介して要素の寿命を制御し、ベクトル関数はポインタを要素に向かってコピーするだけだからです。

26
Tuxer

newを使用してオブジェクトにメモリを割り当てている場合は、ヒープに割り当てています。この場合、ポインターを使用する必要があります。ただし、C++では、通常、スタック上のすべてのオブジェクトを作成し、ヒープ上のオブジェクトへのポインターを渡すのではなく、それらのオブジェクトのコピーを渡します。

なぜこれが良いのですか?これは、C++にはガベージコレクションがないため、オブジェクトをdelete明示的に指定しない限り、ヒープ上のオブジェクトのメモリが再利用されないためです。ただし、スタック上のオブジェクトは、スコープを離れると常に破棄されます。ヒープではなくスタック上にオブジェクトを作成すると、メモリリークのリスクを最小限に抑えることができます。

ヒープの代わりにスタックを使用する場合は、適切なコピーコンストラクターとデストラクターを作成する必要があります。コピーコンストラクタまたはデストラクタが不適切に記述されると、メモリリークまたはダブルフリーが発生する可能性があります。

オブジェクトが大きすぎて効率的にコピーできない場合は、ポインターを使用してもかまいません。ただし、メモリリークを回避するには、参照カウントスマートポインター(C++ 0x auto_ptrまたはBoostライブラリポインターのいずれか)を使用する必要があります。

7
Zhehao Mao

vectorの追加および内部ハウスキーピングは、元のオブジェクトのコピーを使用します-コピーの取得が非常に高価または不可能な場合は、ポインターを使用することをお勧めします。

vectorメンバーをポインターにする場合は、 スマートポインター を使用してコードを簡素化し、リークのリスクを最小限に抑えます。

たぶんあなたのクラスは適切な(つまり深い)コピーの構築/割り当てをしていませんか?その場合、ポインターは機能しますが、ベクトルメンバーとしてのオブジェクトインスタンスは機能しません。

4
Steve Townsend

通常、std::vectorにクラスを直接保存しません。理由は簡単です。クラスが派生しているかどうかはわかりません。

例えば。:

ヘッダー内:

class base
{
public:
  virtual base * clone() { new base(*this); };
  virtual ~base(){};
};
class derived : public base
{
public:
  virtual base * clone() { new derived(*this); };
};
void some_code(void);
void work_on_some_class( base &_arg );

ソース内:

void some_code(void)
{
  ...
  derived instance;
  work_on_some_class(derived instance);
  ...
}

void work_on_some_class( base &_arg )
{
  vector<base> store;
  ...
  store.Push_back(*_arg.clone());
  // Issue!
  // get derived * from clone -> the size of the object would greater than size of base
}

だから私はshared_ptrを使用することを好む:

void work_on_some_class( base &_arg )
{
  vector<shared_ptr<base> > store;
  ...
  store.Push_back(_arg.clone());
  // no issue :)
}
3
Naszta

ベクトルを使用する主なアイデアは、発生しないポインターまたはスマートポインターを使用する場合、オブジェクトを継続スペースに格納することです。

2
Jman