4

私のアプリケーションには、Car、Bicycle、Person など、さまざまなデータ型がたくさんあります (実際には他のデータ型ですが、これは単なる例です)。

私のアプリケーションにはかなりの「ジェネリック」コードもあり、アプリケーションはもともと C で書かれていたため、車、自転車、人などへのポインタは、ID とともにこれらのジェネリック モジュールへの void ポインタとして渡されることがよくあります。次のようなタイプの

Car myCar;
ShowNiceDialog ((void *)&myCar, DATATYPE_CAR);

「ShowNiceDialog」メソッドはメタ情報 (DATATYPE_CAR をインターフェイスにマップして Car から実際のデータを取得する関数) を使用して、指定されたデータ型に基づいて車の情報を取得するようになりました。そうすれば、新しいデータ型ごとに毎回ではなく、ジェネリック ロジックを 1 回だけ記述する必要があります。

もちろん、C++ では、次のように共通のルート クラスを使用することで、これをはるかに簡単にすることができます。

class RootClass
   {
   public:
      string getName() const = 0;
   };

class Car : public RootClass
   {
   ...
   };

void ShowNiceDialog (RootClass *root);

問題は、場合によっては、データ型をクラスに格納したくないが、メモリを節約するためにまったく異なる形式で格納したいことです。場合によっては、アプリケーションで管理する必要がある数億のインスタンスがあり、すべてのインスタンスに対して完全なクラスを作成したくない場合があります。2 つの特性を持つデータ型があるとします。

  • 数量 (double、8 バイト)
  • ブール値 (1 バイト)

この情報を格納するのに 9 バイトしか必要ありませんが、クラスに入れると (パディングのため) 少なくとも 16 バイトが必要になり、v-pointer を使用すると 24 バイトも必要になる可能性があります。数億のインスタンスの場合、すべてのバイトがカウントされます (アプリケーションの 64 ビット バリアントがあり、場合によっては 6 GB のメモリが必要です)。

void-pointer アプローチには、void-pointer でほとんど何でもエンコードし、そこからの情報が必要な場合にそれを使用する方法を決定できるという利点があります (実際のポインターとして、インデックスとして使用するなど)。型安全性のコスト。

汎用ロジックはアプリケーションのかなりの部分を形成するため、テンプレート化されたソリューションは役に立ちません。これらすべてをテンプレート化することは望ましくありません。さらに、データ モデルは実行時に拡張できます。これは、テンプレートが役に立たないことも意味します。

void-pointer よりもこれを処理するためのより良い (そして型安全な) 方法はありますか? これに関するフレームワーク、ホワイトペーパー、研究資料への参照はありますか?

4

4 に答える 4

3

フルクラスが必要ない場合は、FlyWeightパターンを確認する必要があります。メモリを節約するように設計されています。

編集:申し訳ありませんが、ランチタイムの一時停止;)

典型的なFlyWeightアプローチは、多数のオブジェクトに共通するプロパティを、特定のインスタンスに典型的なプロパティから分離することです。

一般的に、それは意味します:

struct Light
{
  kind_type mKind;
  specific1 m1;
  specific2 m2;
};

多くの場合、これkind_typeはポインタですが、必須ではありません。あなたの場合、ポインタ自体が「有用な」情報の4倍になるので、それは本当に無駄になります。

ここでは、パディングを利用してIDを保存できると思います。結局のところ、あなたが言ったように、9ビットしか使用していなくても16ビットに拡張されるので、残りの7ビットを無駄にしないでください!

struct Object
{
  double quantity;
  bool flag;
  unsigned char const id;
};

要素の順序が重要であることに注意してください。

0x00    0x01    0x02    0x03
[      ][      ][      ][      ]
   quantity       flag     id

0x00    0x01    0x02    0x03
[      ][      ][      ][      ]
   id     flag     quantity

0x00            0x02            0x04
[      ][      ][      ][      ][      ][      ]
   id     --        quantity      flag     --

「実行時に拡張」ビットがわかりません。怖いようです。これはある種の自己修正コードですか?

テンプレートを使用すると、 FlyWeightの非常に興味深い形式であるBoost.Variantを作成できます。

typedef boost::variant<Car,Dog,Cycle, ...> types_t;

バリアントは、ここで引用されているタイプのいずれかを保持できます。「通常の」機能で操作できます。

void doSomething(types_t const& t);

コンテナに保管できます:

typedef std::vector<types_t> vector_t;

そして最後に、それを操作する方法:

struct DoSomething: boost::static_visitor<>
{
  void operator()(Dog const& dog) const;

  void operator()(Car const& car) const;
  void operator()(Cycle const& cycle) const;
  void operator()(GenericVehicle const& vehicle) const;

  template <class T>
  void operator()(T const&) {}
};

ここでの動作に注目するのは非常に興味深いことです。したがって、通常の関数のオーバーロード解決が発生します。

  • あなたが持っているCarか、Cycleあなたがそれらを使うなら、他のすべての子供GenericVehicleは私たちに4番目のバージョンを与えます
  • テンプレートのバージョンをすべてキャッチとして指定し、適切に指定することができます。

非テンプレートメソッドは.cppファイルで完全に定義できることに注意してください。

この訪問者を適用するには、次のboost::apply_visitor方法を使用します。

types_t t;
boost::apply_visitor(DoSomething(), t);

// or

boost::apply_visitor(DoSomething())(t);

2番目の方法は奇妙に思えますが、述語として最も興味深い方法で使用できることを意味します。

vector_t vec = /**/;
std::foreach(vec.begin(), vec.end(), boost::apply_visitor(DoSomething()));

バリアントについて読んでください、それは最も興味深いです。

  • コンパイル時チェック:1つ見逃しましたかoperator()?コンパイラがスローします
  • RTTIの必要はありません:仮想ポインタも動的型もありません->ユニオンを使用するのと同じくらい高速ですが、安全性が向上しています

もちろん、複数のバリアントを定義することで、コードをセグメント化できます。コードの一部のセクションが4/5タイプのみを扱う場合は、特定のバリアントを使用してください:)

于 2010-06-03T09:50:12.067 に答える
2

この場合、単純にオーバーロードを使用する必要があるように思えます。例えば:

#ifdef __cplusplus // Only enable this awesome thing for C++:
#   define PROVIDE_OVERLOAD(CLASS,TYPE) \
    inline void ShowNiceDialog(const CLASS& obj){ \ 
         ShowNiceDialog(static_cast<void*>(&obj),TYPE); \
    }

    PROVIDE_OVERLOAD(Car,DATATYPE_CAR)
    PROVIDE_OVERLOAD(Bicycle,DATATYPE_BICYCLE)
    // ...

#undef PROVIDE_OVERLOAD // undefine it so that we don't pollute with macros
#endif // end C++ only 

さまざまな型のオーバーロードを作成すると、シンプルで型安全な方法で ShowNiceDialog を呼び出すことができますが、最適化された C バリアントを引き続き活用することができます。

上記のコードを使用すると、C++ で次のように記述できます。

 Car c;
 // ...
 ShowNiceDialog(c);

の型を変更した場合cでも、適切なオーバーロードが使用されます (または、オーバーロードがない場合はエラーが発生します)。既存の型安全でない C バリアントの使用を妨げるものではありませんが、型安全なバージョンの方が呼び出しが簡単なので、とにかく、他の開発者はそれを好むと思います。

編集
上記は、実装をタイプセーフにする方法ではなく、API をタイプセーフにする方法の質問に答えることを追加する必要があります。これにより、システムを使用しているユーザーが安全でない呼び出しを回避できます。また、これらのラッパーは、コンパイル時に既に知られている型を使用するための型安全な手段を提供することに注意してください...動的型の場合、安全でないバージョンを使用することが実際に必要になります。ただし、別の可能性として、次のようなラッパー クラスを提供できます。

class DynamicObject
{
    public:
         DynamicObject(void* data, int id) : _datatype_id(id), _datatype_data(data) {}
         // ...
         void showNiceDialog()const{ ShowNiceDialog(_datatype_data,_datatype_id); }
         // ...
    private:
         int _datatype_id;
         void* _datatype_data;
};

これらの動的な型の場合、オブジェクトの構築に関してはまだあまり安全ではありませんが、オブジェクトが構築されると、より安全なメカニズムが得られます。API のユーザーが実際に DynamicObject クラス自体を構築することはなく、安全でないコンストラクターを呼び出す必要がないように、これを型安全なファクトリと組み合わせることが合理的です。

于 2010-06-03T09:16:10.833 に答える
1

たとえば、Visual Studio でクラスのパッキングを変更することは完全に可能です。__declspec(align(x)) または #pragma pack(x) を使用でき、プロパティ ページにオプションがあります。

解決策は、たとえば、各データ メンバーのベクトルに個別にクラスを格納することです。各クラスは、マスター クラスへの参照とこれらのベクトルへのインデックスのみを保持します。マスタークラスがシングルトンである場合、これはさらに改善される可能性があります。

class VehicleBase {
public:
    virtual std::string GetCarOwnerFirstName() = 0;
    virtual ~VehicleBase();
};
class Car : public VehicleBase {
    int index;
public:
    std::string GetCarOwnerFirstName() { return GetSingleton().carownerfirstnames[index]; }
};

もちろん、これには、Car のデータ メンバーのメモリ管理など、いくつかの実装の詳細が必要です。ただし、Car 自体は自明であり、いつでも作成/破棄できます。GetSingleton のベクトルはデータ メンバーを非常に効率的にパックします。

于 2010-06-03T12:14:24.997 に答える
0

私は特性を使用します

template <class T>
struct DataTypeTraits
{
};

template <>
struct DataTypeTraits<Car>
{
   // put things that describe Car here
   // Example: Give the type a name
   static std::string getTypeName()
   {
      return "Car";
   }
};
template <>
struct DataTypeTraits<Bicycle>
{
   // the same for bicycles
   static std::string getTypeName()
   {
      return "Bicycle";
   }
};

template <class T>
ShowNiceDialog(const T& t)
{
   // Extract details of given object
   std::string typeName(DataTypeTraits<T>::getTypeName());
   // more stuff
}

このように、適用したい新しいタイプを追加するたびに ShowNiceDialog() を変更する必要はありません。必要なのは、新しい型に対する DataTypeTraits の特殊化だけです。

于 2010-06-03T09:30:38.667 に答える