c ++ 11があるので、c++11のboost::ptr_containersの代わりがあるかどうか自問していました。たとえば、を使用できることはわかっていますstd::vector<std::unique_ptr<T> >
が、これが完全な代替品であるかどうかはわかりません。これらのケースを処理するための推奨される方法は何ですか?
2 に答える
いくつかのポリモーフィックオブジェクトを(ヒープへのポインターによって)コンテナーに入れ、そのコンテナーをstd::algorithmで使用する短いプログラムを作成することにしました。std::remove_if
例として選びました。
これが私がそれをどのように行うかですvector<unique_ptr<T>>
:
#include <vector>
#include <memory>
#include <iostream>
class Animal
{
public:
Animal() = default;
Animal(const Animal&) = delete;
Animal& operator=(const Animal&) = delete;
virtual ~Animal() = default;
virtual void speak() const = 0;
};
class Cat
: public Animal
{
public:
virtual void speak() const {std::cout << "Meow\n";}
virtual ~Cat() {std::cout << "destruct Cat\n";}
};
class Dog
: public Animal
{
public:
virtual void speak() const {std::cout << "Bark\n";}
virtual ~Dog() {std::cout << "destruct Dog\n";}
};
class Sheep
: public Animal
{
public:
virtual void speak() const {std::cout << "Baa\n";}
virtual ~Sheep() {std::cout << "destruct Sheep\n";}
};
int main()
{
typedef std::unique_ptr<Animal> Ptr;
std::vector<Ptr> v;
v.push_back(Ptr(new Cat));
v.push_back(Ptr(new Sheep));
v.push_back(Ptr(new Dog));
v.push_back(Ptr(new Sheep));
v.push_back(Ptr(new Cat));
v.push_back(Ptr(new Dog));
for (auto const& p : v)
p->speak();
std::cout << "Remove all sheep\n";
v.erase(
std::remove_if(v.begin(), v.end(),
[](Ptr& p)
{return dynamic_cast<Sheep*>(p.get());}),
v.end());
for (auto const& p : v)
p->speak();
}
これは以下を出力します:
Meow
Baa
Bark
Baa
Meow
Bark
Remove all sheep
destruct Sheep
destruct Sheep
Meow
Bark
Meow
Bark
destruct Dog
destruct Cat
destruct Dog
destruct Cat
それは私には似合います。ptr_vector
しかし、私はこれを問題のあるものに翻訳することに気づきました:
boost::ptr_vector<Animal> v;
v.push_back(new Cat);
v.push_back(new Sheep);
v.push_back(new Dog);
v.push_back(new Sheep);
v.push_back(new Cat);
v.push_back(new Dog);
for (auto const& p : v)
p.speak();
std::cout << "Remove all sheep\n";
v.erase(
std::remove_if(v.begin(), v.end(),
[](Animal& p)
{return dynamic_cast<Sheep*>(&p);}),
v.end());
for (auto const& p : v)
p.speak();
algorithm:1897:26: error: overload resolution selected deleted operator '='
*__first = _VSTD::move(*__i);
~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~
test.cpp:75:9: note: in instantiation of function template specialization 'std::__1::remove_if<boost::void_ptr_iterator<std::__1::__wrap_iter<void
**>, Animal>, Sheep *(^)(Animal &)>' requested here
std::remove_if(v.begin(), v.end(),
^
test.cpp:12:13: note: candidate function has been explicitly deleted
Animal& operator=(const Animal&) = delete;
^
1 error generated.
問題は次の機能ですboost::ptr_vector
。イテレータは内部に格納されたポインタを返しません。逆参照されたポインタを返します。したがって、コンテナがで使用される場合std::algorithms
、アルゴリズムは、オブジェクトへの格納されたポインタではなく、格納されたオブジェクトをコピーしようとします。
誤ってポリモーフィックオブジェクトをコピー不可にするのを忘れた場合、コピーセマンティクスが自動的に提供され、コンパイル時エラーではなく実行時エラーが発生します。
class Animal
{
public:
Animal() = default;
virtual ~Animal() = default;
virtual void speak() const = 0;
};
これにより、この誤った出力が発生します。
Meow
Baa
Bark
Baa
Meow
Bark
Remove all sheep
destruct Cat
destruct Dog
Meow
Baa
Bark
Baa
destruct Cat
destruct Sheep
destruct Dog
destruct Sheep
この実行時エラーは、を使用している場合は発生しませんvector<unique_ptr>
。
ポインターのコンテナーを格納するが、参照のコンテナーを提示することのインピーダンスの不一致は、一般的なアルゴリズムでコンテナーを安全に使用することとは相容れないように見えます。実際、これが、ptr_containersに多くのアルゴリズムのカスタムバージョンが付属している理由です。ptr_containersでこのジョブを実行する正しい方法は、これらのメンバーアルゴリズムのみを使用することです。
v.erase_if([](Animal& p)
{return dynamic_cast<Sheep*>(&p);});
ptr_containersのメンバーとして提供されていない変異シーケンスアルゴリズムが必要な場合は、の<algorithm>
アルゴリズム、または他のサードパーティによって提供されている一般的なアルゴリズムに手を伸ばそうとしないでください。
要約すると、boost :: ptr_containersは、他の唯一の実用的なオプションがであったときに、真のニーズを満たしましたstd::vector<boost::shared_ptr<T>>
。ただし、これstd::vector<std::unique_ptr<T>>
で、オーバーヘッドの引数はなくなりました。また、C++11ソリューションには安全性と柔軟性の両方の利点があるようです。「クローンセマンティクス」が必要な場合は、独自clone_ptr<T>
に作成して、stdコンテナとアルゴリズムで使用することを真剣に検討します。
std :: libを再利用すると、ブーストlibよりもコンテナーのオプションが開いたままになり(unordered_set / map、forward_listなど)、std::algorithmsのオプションが可能な限り広く開いたままになります。
そうは言っても、すでにboost :: ptr_containersを使用して作業中のデバッグ済みコードがある場合は、緊急に変更する必要はありません。
彼らは本当に2つの類似しているが異なる問題を解決します。
ポインタコンテナは、値ではなく割り当てられたメモリへのポインタであるオブジェクトをコンテナに格納する方法です。彼らは彼らがポインターの入れ物であるという事実を隠すために彼らの力ですべてをします。これの意味は:
- コンテナ内のエントリをNULLにすることはできません。
- イテレータと関数から取得する値は、型へのポインタではなく、型への参照です。
- 多くの標準アルゴリズムでの作業は...注意が必要です。そして「トリッキー」とは、壊れているという意味です。ポインタコンテナには、独自のアルゴリズムが組み込まれています。
ただし、ポインターコンテナーは、ポインターコンテナーであることを認識しているため、いくつかの新しい機能を提供できます。
clone
オブジェクトのタイプに特定の「クローン可能」概念を使用して、ディープコピーを実行するメンバー関数。- オブジェクトの所有権を解放するコンテナの機能(たとえば、浅いコピーの後)。
- 所有権を他のコンテナに譲渡するための組み込み関数。
それらは実際にはまったく異なる概念です。ポインタコンテナが特殊な関数で自動的に実行できるように、手動で実行しなければならないことがたくさんあります。
ポインタのコンテナが本当に必要な場合は、のコンテナを使用できますunique_ptr
。しかし、たまたまヒープ割り当てされたオブジェクトの束を格納する必要があり、所有権などを含む特別なゲームをそれらでプレイしたい場合は、ポインターコンテナーは悪い考えではありません。