サンプルソフトウェアレンダラーを作成して、C++のスキルを向上させたいと考えています。 3Dスペース内のポイントで構成されるオブジェクトを取得して2Dビューポートにマッピングし、ビュー内の各ポイントにさまざまなサイズの円を描画します。どちらが良いですか:
class World{
vector<ObjectBaseClass> object_list;
public:
void generate(){
object_list.clear();
object_list.Push_back(DerivedClass1());
object_list.Push_back(DerivedClass2());
または...
class World{
vector<ObjectBaseClass*> object_list;
public:
void generate(){
object_list.clear();
object_list.Push_back(new DerivedClass1());
object_list.Push_back(new DerivedClass2());
??ベクトルは最初の例では自動的にDerivedClassデストラクタを呼び出しますが2番目の例では呼び出さないため、2番目の例でポインタを使用して新しいオブジェクトを作成すると、ベクトルを使用するポイントが無効になりますか?アクセス方法を使用する限り、ベクターはメモリ管理を処理するため、ベクターを使用するときに新しいオブジェクトへのポインターは必要ですか?今、私が世界に別の方法を持っているとしましょう:
void drawfrom(Viewport& view){
for (unsigned int i=0;i<object_list.size();++i){
object_list.at(i).draw(view);
}
}
これが呼び出されると、ワールドリスト内のすべてのオブジェクトに対してdrawメソッドが実行されます。派生クラスが独自のバージョンのdraw()を持つことができるようにしたいとします。メソッドセレクター(->)を使用するには、リストをポインターにする必要がありますか?
あなたはこのコードであなたが望むものを手に入れません
class World{
vector<ObjectBaseClass> object_list;
public:
void generate(){
object_list.clear();
object_list.Push_back(DerivedClass1());
object_list.Push_back(DerivedClass2());
何が起こるかは、オブジェクトスライスと呼ばれます。 ObjectBaseClassのベクターを取得します。
ポリモーフィズムを機能させるには、ある種のポインタを使用する必要があります。おそらくいくつかのスマートポインターまたは参照がboostまたは他のライブラリーにあり、使用してコードを2番目に提案されたソリューションよりもはるかに安全にすることができます。
C++を改善することを明示的に述べているので、 Boost の使用を開始することをお勧めします。これにより、3つの異なる方法で問題を解決できます。
shared_ptr
の使用shared_ptr
を使用すると、次のようにベクターを宣言できます。
std::vector< boost::shared_ptr< ObjectBase > > object_list;
次のように使用します。
typedef std::vector< boost::shared_ptr< ObjectBase > >::iterator ObjectIterator;
for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
(*it)->draw(view);
これはポリモーフィズムを提供し、通常のポインターのベクトルと同じように使用されますが、shared_ptr
はメモリ管理を行い、オブジェクトを参照している最後のshared_ptr
が破棄されるとオブジェクトを破棄します。
C++ 11に関する注意:C++ 11では、shared_ptr
がstd::shared_ptr
として規格の一部になったため、Boostはこのアプローチではもはや必要ありません。ただし、共有所有権が本当に必要な場合を除き、C++ 11で新しく導入されたstd::unique_ptr
を使用することをお勧めします。
ptr_vector
の使用ptr_vector
を使用すると、次のようになります。
boost::ptr_vector< ObjectBase > object_list;
次のように使用します。
typedef boost::ptr_vector< ObjectBase >::iterator ObjectIterator;
for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
(*it)->draw(view);
これもポインタの通常のベクトルのように使用されますが、今回はptr_vector
がオブジェクトの寿命を管理します。最初のアプローチとの違いは、ベクターが破棄されるとオブジェクトが破棄されることですが、オブジェクトを参照する他のshared_ptr
sが存在する場合、それらはコンテナーよりも長く存続する可能性があります。
reference_wrapper
の使用reference_wrapper
を使用して、次のように宣言します。
std::vector< boost::reference_wrapper< ObjectBase > > object_list;
そして、次のように使用します。
typedef std::vector< boost::reference_wrapper< ObjectBase > >::iterator
ObjectIterator;
for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
it->draw(view);
上記のアプローチのように、最初にイテレータを逆参照する必要がないことに注意してください。ただし、これが機能するのは、オブジェクトの有効期間が別の場所で管理されており、vector
よりも長いことが保証されている場合のみです。
C++ 11に関する注意:reference_wrapper
もC++ 11で標準化され、std::reference_wrapper
として使用できるようになりました。ブースト。
Maciej H sの回答で指摘されているように、最初のアプローチでは object slicing になります。一般に、コンテナーを使用する場合は、 iterators を確認する必要があります。
最初の質問に関しては、動的に割り当てられたオブジェクト(つまり、ポインタを格納するためにnot)ではなく、自動的に割り当てられたオブジェクトを使用することが一般的に望ましい問題のタイプについては、コピーの構築と割り当てが可能であり、法外に高価ではありません。
オブジェクトをコピーまたは割り当てることができない場合は、とにかくそれらを直接std::vector
に入れることはできないため、問題は疑わしいものです。コピー操作や割り当て操作に負荷がかかる場合(オブジェクトに大量のデータが格納される場合など)は、効率上の理由からポインターを格納することをお勧めします。それ以外の場合は、一般的に、あなたが言及した理由に基づいてポインタを保存しない方がよい(自動割り当て解除)
2番目の質問については、はい、それがポインタを格納するもう1つの有効な理由です。動的ディスパッチ(仮想メソッド呼び出し)は、ポインターと参照でのみ機能します(参照をstd::vector
に格納することはできません)。複数の多相型のオブジェクトを同じベクターに格納する必要がある場合は、スライスを回避するためにポインターを格納する必要があります。
まあ、それはあなたがあなたのベクトルで何をしようとしているのかに依存します。
ポインターを使用しない場合は、渡したオブジェクトのcopyがベクターに配置されます。それが単純なオブジェクトである場合、および/またはそれらのストレージを追跡することに煩わされたくない場合、これはまさにあなたが望むものであるかもしれません。複雑である場合、または構築と破棄に非常に時間がかかる場合は、その作業をそれぞれ1回だけ行い、ポインターをベクターに渡すことをお勧めします。