web-dev-qa-db-ja.com

C ++でのメモリ内テーブルの設計

インメモリデータベースを構造化するためのオプションを評価していますが、それを実装する方法についていくつかのアイデアがあります。最適なデザインの選択についてのあなたの意見を知りたいのですが。

さまざまな列タイプを表すようにパラメーター化された列クラスがあります。

template<typename T>
class Column<T> {
public:
   std::string name();
   T sum();
   T avg();
   ...
private:
   std::string name;
   std::vector<T> vec;
   ...
};

型パラメーターが異なるColumnのベクターを格納するための最適なルートがよくわかりません。たとえば、3列のテーブルには、1つの整数列、フロート列、および文字列列がある場合があります。

Boost :: variantがあることは知っていますが、boostの使用は許可されていません。

次のいずれかを使用することを考えていました。

  1. タグ付きユニオン
  2. 純粋なオブジェクト指向:IntColumn:Columnなどのように列を拡張します。

あなたの考えは何ですか?より良いアイデアを得ましたか?

5
jimjampez

列の型はテンプレートパラメータであるため、C++型システム内で列の型をモデル化しています。これはいい。 _Column<int>_と_Column<std::string>_は異なる型です。すべての列タイプに共通するプロパティがある場合(たとえば、列に名前がある場合)、これらを基本クラスに抽出して、共通のタイプを介してこれらの共通操作にアクセスできるようにすることができます。ただし、get()sum()などのタイプ固有の操作はこのベースに存在できず、テンプレート化された_Column<T>_の一部である必要があります。

異なるタイプの列を持つテーブルタイプがある場合、テンプレートパラメータへのアクセスを必ず失うため、これらに同じタイプを強制することは明らかに賢明ではありません(「タイプ消去」)。代わりに、さまざまな型を採用し、Tableも強く型付けします。 _std::Tuple<T...>_のようなコンテナーがここで役立ちます。

列タイプの独立したパーツにアクセスする必要がある場合は、基本タイプとして使用できる列へのポインターをいつでも取得できます。

C++ 14を使用したスケッチ(C++ 11では、いくつかの便利な関数を自分で実装する必要がありますが、_std::Tuple_およびテンプレートパラメータパックがあります):

_class ColumnBase {
  ...
public:
  std::string name() { … }
};

template<class T>
class Column : public ColumnBase {
  std::vector<T> m_items;
  ...
};

template<class... T>
class Table {
  std::Tuple<Column<T>...> m_columns;

  template<std::size_t... index>
  std::vector<ColumnBase*> columns_vec_helper(std::index_sequence<index...>) {
    return { (&std::get<index>(m_columns))... };
  }

public:
  std::vector<ColumnBase*> columns_vec() {
    return columns_vec_helper(std::make_index_sequence<sizeof...(T)>{});
  }
};
_

次に、すべての列の名前を出力できます。

_for (const auto& colBase : table.columns_vec())
  std::cout << "column " << colBase->name() << "\n";
_

各列タイプを個別に処理する必要はありません。

ideoneで実行可能なデモ

テンプレートのみが、整数列からintを取得するタイプセーフを提供します。対照的に、共用体/バリアント型では、使用可能なコードをすべて記憶するためにコードを使用する必要があります(テンプレートを使用すると、型チェッカーはすべてを処理するように強制します)。サブタイピングでは、実装を共有する列タイプの特定の操作を使用できません。つまりメソッドint IntColumn::get(std::size_t i)および関連するメソッドconst std::string& StringColumn::get(std::size_t i)は、共通のインターフェースを持っているように見えるかもしれませんが、それは偶発的なものであり、強制することはできません。特に、C++での仮想メソッドとテンプレートの任意の組み合わせは、非常に醜く、非常に速くなります。

テンプレートの欠点は、一般的なコードを注意深く書く必要があり、テンプレートのメタプログラミングを行う必要があることです。正しく実行すると、結果は驚くほど使いやすくなりますが、実装は高度なC++になります。あなたのデザインがそれほど熟練していないプログラマーによって保守されることを意図している場合(私が数か月でこのコードを振り返るときと同じように困惑するでしょう)、そのような「賢い」ソリューションを避ける方が賢明かもしれません。その利点にもかかわらず、より伝統的なOOPパターンを使用していますが、同様の構造を提供しますが、機能するためにいくつかの_static_cast_ sが必要になる場合があります。

5
amon

私は強く提示された@amonのアプローチを支持しますが、そのルートをたどることができなかった状況、たとえば、実行時まで知られています。

その場合、すでに説明したように、boost:: variantboost::anyなどの機能が適切なソリューションを提供する可能性があります。

ブーストを使用できないという制約があるように思われるので、自分でロールバックしてみませんか? 2つの基本的なアプローチは、タグ付きユニオンを使用するか、ポリモーフィック基本クラス(および明確に定義されたインターフェイスまたはdynamic_casts、おそらく非循環ビジターの背後に隠されている)を使用してC++の動的型システムを利用することです。

私は参照しています 私の答え on SO両方のアプローチの基本的なスケッチを示し、より完全なboost::anyのような実装へのリンクを示します。

1
Daniel Jour