1

以前の質問 ( const 変数を持つクラスのプログラミング モデル) には完璧な回答がありましたが、今では新しい要件があり、回答が機能していないようです。

いくつかの const 変数を含むクラスがあるとします。

class Base
{
    protected:
        const int a, b;
    public:
        Base(string file);
};

定数は初期化リストで初期化する必要がありますが、値を計算するために事前に他の方法も必要です。

答えは、ヘルパー クラスを使用することでした。

class FileParser 
{
public:
  FileParser (const string& file)
  {
    Parse (file);
  }

  int GetA () const { return mA; }
  int GetB () const { return mB; }

private:
  int mA;
  int mB;

  void Parse (const string& file)
  {
    // MAGIC HAPPENS!
    // Parse the file, compute mA and mB, then return
  }
};

これで問題は完全に解決しましたが、定数の数と型が異なる Base から派生した一連のクラスがあり、同じヘルパー (FileParser) クラスを使用したい場合はどうでしょうか? ブースト C++ は使用できませんが、c++11 は持っています。可変長のタプルを返す変数付きのテンプレートを試してみましたが、自明ではないようです。以下は、私が試した変更されたヘルパー クラスです。

template <typename ... Types>
class LineParser
{
    private:
        std::tuple<Types...> _t; 
    public:
        LineParser(const std::string & line)
        {   
            // local variables
            std::stringstream ss; 

            // parse the line
            ss.str(line);
            for (int i=0; i<sizeof...(Types); i++)
            {   
                ss>>std::get<i>(_t);
            }   
        }   
};

コンパイルに失敗しました:

error: the value of ‘i’ is not usable in a constant expression

この問題を解決できません。別の解決策を探しているかもしれません.c++

4

1 に答える 1

2

そのため、これはもう少し複雑になっています。これも少しXY 問題ですが、少なくともここでは X と Y の両方について明確にしています。

提案されたアプローチから始めましょう。これは決してうまくいきません:

std::get<i>(_t);

getは関数テンプレートであるためi、整数定数式である必要があります。つまり、iコンパイル時に認識されている必要があります。

提案されたソリューションは基本的に に基づいているため、ICEtupleを作成できないと、すべてが解けてバラバラになります。iそこで、提案されたアプローチを忘れて、問題をもう一度見てみましょう。おそらくフィールドのように見えるものに分かれていると思われる、たくさんのものを含むファイルがあります。これらのフィールドは (私が知る限り) さまざまな種類のデータを表しています。これがそのようなファイルの例であるとします:

IBM
123.45
1000

ここには、文字列、浮動小数点数、および整数があります。異なるファイルは完全に異なるデータを持つ可能性があり、あるファイルの特定の位置にあるデータは、別のファイルの同じ位置にあるデータと同じ型ではない場合があります。次に、これらの異なるファイルで初期化する必要があるさまざまなクラスの束があり、それぞれがファイル内のさまざまな位置からプルされた、さまざまな型のさまざまなデータ メンバーの独自のコレクションを持っています。うん。

問題の複雑さを考えると、私の自然な傾向は、解決策をできるだけ単純に保つことです。ここにはすでに十分な複雑さがあります。私が考えることができる最も簡単なアプローチは、LineParser解析したいファイルの種類ごとに異なる具体的なクラスを持つことです。ただし、これにより、さまざまな種類のファイルが多数ある場合にコードが肥大化し、その数が増えるにつれて保守が指数関数的に難しくなります。だから、あなたがそれをしたくないという前提で続けましょう。

ただし、増加しないことの 1 つは、ファイル内のさまざまな種類のフィールドの数です。最終的には、文字列、整数、浮動小数点数、およびドメインに固有のその他の特別なものなど、実際にはいくつかしかありません。ただし、データ ファイルを追加しても、フィールドの種類の数は比較的一定のままです。もう 1 つの定数はファイル自体です。これは文字データです。では、それを活用しましょう。

ファイル ストレージ タイプ (ここでは文字データを想定しています) からさまざまなフィールドに変換するいくつかの無料関数を実装します。Boost を使用している場合は、これを使用lexical_castしてほとんどのことを実行できます。それ以外の場合は、stringstreamまたは他のものを使用できます。考えられる実装の 1 つを次に示します。他にも多くの実装があります。

template <typename Return> Return As (const std::string& val)
{
  std::stringstream ss;
  ss << val;
  Return retval;
  ss >> retval;
  return retval;
}

Baseここで、特定の-type クラスについて、関心のあるフィールドの位置とタイプがわかっていると想定しています。これらは不変です。たとえばBase、株価を表す の場合、最初のフィールドがティッカー シンボルであり、文字列であることがわかっています。

FileParserファイルからすべてを取り出し、ファイル内のフィールドごとに 1 つの要素を配列内の文字データとしてキャッシュするだけであれば、クラスをジェネリックにすることができます。繰り返しますが、ここには多くの可能な実装があります。私の焦点は設計であり、実際のコードではありません。

class LineParser
{
    private:
        std::vector <string> mItems;
    public:
        LineParser(const std::string & fileName)
        {   
          std::ifstream fs(fileName);
          std::copy(
            std::istream_iterator<int>(fs), 
            std::istream_iterator<int>(), 
            std::back_inserter(mItems));
        }   

        std::string GetAt (size_t i) const
        { 
          return mItems [i];
        }
};

Baseコンストラクターで、各constデータ メンバーに対して、特定のアイテムを からプルLineParserし、自由関数で変換します。

class Base
{ 
private:
  const std::string mTicker;
  const uint32_t mSize;
  const float mPrice;
public:
  Base (const LineParser& parser)
  : 
    mTicker (As <std::string> (parser.GetAt (0))),  // We know the ticker is at field 0
    mPrice (As <float> (parser.GetAt (1))),  // Price is at field 1...
    mSize (As <uint32_t> (parser.GetAt (2))
  {
  }
};

このアプローチには、私が気に入っている点がいくつかあります。1 つは、関係するクラスと関数が多数あるにもかかわらず、それぞれが単純であることです。ここにある小さなギズモにはそれぞれ、明確に定義された 1 つの責任があり、あまり多くのことをしようとはしません。

別の理由として、ビジネス ロジック コードの自己文書化は簡潔であり、それが属する場所:constメンバーが初期化されるコード内:

 Base (const LineParser& parser)
 : 
   mTicker (As <std::string> (parser.GetAt (0))),  // We know the ticker is at field 0
   mPrice (As <float> (parser.GetAt (1))),  // Price is at field 1...
   mSize (As <uint32_t> (parser.GetAt (2))
 {
 }

mTickerたとえば、イニシャライザは、「ティッカー シンボルは文字列であり、ファイルの位置 1 から取得されます」と言います。掃除。

于 2013-10-17T17:31:40.357 に答える