C ++での任意のオブジェクトのリストの保存
-
03-07-2019 - |
質問
Javaでは、オブジェクトのリストを作成できます。複数のタイプのオブジェクトを追加してから、それらを取得し、タイプをチェックして、そのタイプに対して適切なアクションを実行できます。
例:(コードが正確に正しくない場合、謝罪します、私はメモリから行きます)
List<Object> list = new LinkedList<Object>();
list.add("Hello World!");
list.add(7);
list.add(true);
for (object o : list)
{
if (o instanceof int)
; // Do stuff if it's an int
else if (o instanceof String)
; // Do stuff if it's a string
else if (o instanceof boolean)
; // Do stuff if it's a boolean
}
C ++でこの動作を再現する最良の方法は何ですか?
解決
Boost.Variantと訪問者を使用した例:
#include <string>
#include <list>
#include <boost/variant.hpp>
#include <boost/foreach.hpp>
using namespace std;
using namespace boost;
typedef variant<string, int, bool> object;
struct vis : public static_visitor<>
{
void operator() (string s) const { /* do string stuff */ }
void operator() (int i) const { /* do int stuff */ }
void operator() (bool b) const { /* do bool stuff */ }
};
int main()
{
list<object> List;
List.push_back("Hello World!");
List.push_back(7);
List.push_back(true);
BOOST_FOREACH (object& o, List) {
apply_visitor(vis(), o);
}
return 0;
}
この手法を使用する利点の1つは、後でバリアントに別の型を追加し、その型を含めるようにビジターを変更し忘れると、コンパイルされないことです。あらゆる可能性のあるケースをサポートする 。一方、スイッチまたはカスケードifステートメントを使用する場合、どこでも変更を加えてバグを導入することを忘れがちです。
他のヒント
boost::variant
はdirkgentlyのboost::any
の提案ですが、Visitorパターンをサポートしています。つまり、後でタイプ固有のコードを追加する方が簡単です。また、動的割り当てを使用するのではなく、スタックに値を割り当てます。これにより、コードがわずかに効率的になります。
編集: litbがコメントで指摘しているように、variant
の代わりにany
を使用すると、事前に指定されたタイプのリストの1つの値のみを保持できます。これは多くの場合、長所ですが、アスカーの場合は短所かもしれません。
例を次に示します(ただし、Visitorパターンは使用しません):
#include <vector>
#include <string>
#include <boost/variant.hpp>
using namespace std;
using namespace boost;
...
vector<variant<int, string, bool> > v;
for (int i = 0; i < v.size(); ++i) {
if (int* pi = get<int>(v[i])) {
// Do stuff with *pi
} else if (string* si = get<string>(v[i])) {
// Do stuff with *si
} else if (bool* bi = get<bool>(v[i])) {
// Do stuff with *bi
}
}
(そして、はい、技術的にvector<T>::size_type
の代わりにint
の代わりにi
を使用する必要があり、とにかく技術的にvector<T>::iterator
を使用する必要がありますが、私はそれをシンプルにしようとしています) / p>
C ++は異種コンテナをサポートしていません。
boost
を使用しない場合、ハックはダミークラスを作成し、このダミークラスからすべての異なるクラスを派生させることです。ダミークラスオブジェクトを保持するために選択したコンテナを作成し、準備ができました。
class Dummy {
virtual void whoami() = 0;
};
class Lizard : public Dummy {
virtual void whoami() { std::cout << "I'm a lizard!\n"; }
};
class Transporter : public Dummy {
virtual void whoami() { std::cout << "I'm Jason Statham!\n"; }
};
int main() {
std::list<Dummy*> hateList;
hateList.insert(new Transporter());
hateList.insert(new Lizard());
std::for_each(hateList.begin(), hateList.end(),
std::mem_fun(&Dummy::whoami));
// yes, I'm leaking memory, but that's besides the point
}
boost::any
を使用する場合は、 boost::variant
。 ここは、<= >。
この優れた記事は、関心のある2つの主要なC ++エキスパートによって発見される可能性があります。
>さて、<=>は j_random_hacker が言及したように、もう1つ注意すべき点です。したがって、比較使用するものの公正なアイデアを得る。
<=>を使用すると、上記のコードは次のようになります。
class Lizard {
void whoami() { std::cout << "I'm a lizard!\n"; }
};
class Transporter {
void whoami() { std::cout << "I'm Jason Statham!\n"; }
};
int main() {
std::vector< boost::variant<Lizard, Transporter> > hateList;
hateList.push_back(Lizard());
hateList.push_back(Transporter());
std::for_each(hateList.begin(), hateList.end(), std::mem_fun(&Dummy::whoami));
}
この種のことは実際にどれくらいの頻度で有用ですか?私はかなりの数年間、さまざまなプロジェクトでC ++でプログラミングしてきましたが、実際に異種コンテナーを望んだことはありません。何らかの理由でJavaで一般的かもしれませんが(Javaの経験がはるかに少ない)、Javaプロジェクトで使用する場合は、C ++でより適切に動作する異なる方法を実行する方法があるかもしれません。
C ++はJavaよりもタイプセーフを重視しており、これは非常にタイプセーフではありません。
とはいえ、オブジェクトに共通点がない場合、なぜ一緒に保存するのですか?
それらに共通点がある場合、継承するクラスを作成できます。または、boost :: anyを使用します。継承する場合、呼び出す仮想関数を使用するか、dynamic_cast <!> lt; <!> gt;を使用します。本当に必要な場合。
型に基づいて分岐するために動的型キャストを使用すると、多くの場合、アーキテクチャの欠陥が示唆されることを指摘したいと思います。ほとんどの場合、仮想関数を使用して同じ効果を実現できます。
class MyData
{
public:
// base classes of polymorphic types should have a virtual destructor
virtual ~MyData() {}
// hand off to protected implementation in derived classes
void DoSomething() { this->OnDoSomething(); }
protected:
// abstract, force implementation in derived classes
virtual void OnDoSomething() = 0;
};
class MyIntData : public MyData
{
protected:
// do something to int data
virtual void OnDoSomething() { ... }
private:
int data;
};
class MyComplexData : public MyData
{
protected:
// do something to Complex data
virtual void OnDoSomething() { ... }
private:
Complex data;
};
void main()
{
// alloc data objects
MyData* myData[ 2 ] =
{
new MyIntData()
, new MyComplexData()
};
// process data objects
for ( int i = 0; i < 2; ++i ) // for each data object
{
myData[ i ]->DoSomething(); // no type cast needed
}
// delete data objects
delete myData[0];
delete myData[1];
};
残念ながら、C ++でこれを行う簡単な方法はありません。自分で基本クラスを作成し、このクラスから他のすべてのクラスを派生させる必要があります。基本クラスポインターのベクトルを作成し、dynamic_cast(独自のランタイムオーバーヘッドが付属)を使用して実際の型を見つけます。
このトピックの完全性のために、void *を使用して実際に純粋なCでこれを行うことができ、それを必要なものにキャストできることを述べたいと思いますしかし、それは私にいくつかのコードを節約します)。これは、オブジェクトのタイプがわかっている場合、またはそれを記憶するフィールドをどこかに保存している場合に機能します。絶対にこれを行いたくないのですが、それが可能であることを示す例があります:
#include <iostream>
#include <vector>
using namespace std;
int main() {
int a = 4;
string str = "hello";
vector<void*> list;
list.push_back( (void*) &a );
list.push_back( (void*) &str );
cout << * (int*) list[0] << "\t" << * (string*) list[1] << endl;
return 0;
}
コンテナにプリミティブ型を格納することはできませんが、Javaのオートボックス化されたプリミティブ型に似たプリミティブ型ラッパークラスを作成できます(この例では、プリミティブ型リテラルは実際にオートボックス化されています)。プリミティブ変数/データメンバーのように、C ++コードに現れる(そして(ほとんど)使用できる)インスタンス。
組み込み型のオブジェクトラッパーを参照してください。 C ++のオブジェクト指向設計パターンを使用したデータ構造とアルゴリズムから。
ラップされたオブジェクトでは、c ++ typeid()演算子を使用して型を比較できます。
次の比較がうまくいくと確信しています。
if (typeid(o) == typeid(Int))
[ここで、Intはintプリミティブ型などのラップされたクラスです...]
(それ以外の場合は、typeidを返す関数をプリミティブラッパーに追加するだけです。
if (o.get_typeid() == typeid(Int))
...
とはいえ、あなたの例に関して言えば、これは私にとってコードの匂いがします。 これがオブジェクトのタイプを確認する唯一の場所でない限り、 ポリモーフィズムを使用する傾向があります(特に、タイプに固有の他のメソッド/関数がある場合)。この場合、ラップされた各プリミティブクラスによって実装される( 'do stuff'を実行するための)遅延メソッドを宣言するインターフェイスクラスを追加するプリミティブラッパーを使用します。これにより、コンテナイテレータを使用してifステートメントを削除できます(ここでも、型の比較が1つしかない場合、このためにポリモーフィズムを使用して遅延メソッドを設定するのはやり過ぎです)。
私はかなり経験が浅いですが、ここに私が一緒に行くものがあります-
- 操作する必要があるすべてのクラスの基本クラスを作成します。
- コンテナクラスの作成/コンテナクラスの再利用。 (他の答えを見た後に改訂された-私の以前のポイントは難解すぎた。)
- 同様のコードを記述します。
はるかに優れたソリューションが可能であると確信しています。また、より良い説明が可能であると確信しています。悪いC ++プログラミングの習慣があることを知ったので、コードに入らずにアイデアを伝えようとしました。
これが役立つことを願っています。
事実以外にも、ほとんどの人が指摘しているように、あなたはそれをすることはできません。または、もっと重要なことは、あなたが本当にしたくないことです。
例を無視して、実際の例により近いものを考えてみましょう。具体的には、実際のオープンソースプロジェクトで見たコードです。文字配列のCPUをエミュレートしようとしました。したがって、配列に1バイトの<!> quot; op code <!> quot;を追加し、その後に基づいて0、1、または2バイトを文字、整数、または文字列へのポインタにすることができますオペコード。それを処理するために、多くのビットをいじる必要がありました。
私の簡単な解決策:4つの個別のスタック<!> lt; <!> gt; s:<!> quot; opcode <!> quot;列挙型と、chars、ints、stringにそれぞれ1つ。オペコードスタックから次のものを取り出すと、オペランドを取得するために他の3つのうちのどれが使用されます。
同様の方法で実際の問題を処理できる可能性は非常に高いです。
まあ、基本クラスを作成してから、それを継承するクラスを作成できます。次に、それらをstd :: vectorに保存します。
簡単な答えは...できません。
長い答えは...すべてがベースオブジェクトから継承するオブジェクトの独自の新しい階層を定義する必要があるということです。 Javaでは、すべてのオブジェクトは最終的に<!> quot; Object <!> quot;から派生するため、これを行うことができます。
C ++のRTTI(実行時型情報)、特にクロスコンパイラは常に困難でした。
最良のオプションは、STLを使用し、オブジェクトタイプを決定するためにインターフェースを定義することです。
public class IThing
{
virtual bool isA(const char* typeName);
}
void myFunc()
{
std::vector<IThing> things;
// ...
things.add(new FrogThing());
things.add(new LizardThing());
// ...
for (int i = 0; i < things.length(); i++)
{
IThing* pThing = things[i];
if (pThing->isA("lizard"))
{
// do this
}
// etc
}
}
マイク