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)类呢?我不能使用 boost 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必须在编译时知道。

由于您提出的解决方案基本上基于 a ,因此当您无法制作和 ICEtuple时,整个事情就会瓦解并分崩离析。i所以,让我们忘记提出的方法,再看看这个问题。你有一个文件,里面有一堆东西,大概分成了一些看起来像字段的东西。这些字段代表(据我所知)不同类型的数据。假设这是此类文件的示例:

IBM
123.45
1000

这里我们有一个字符串、一个浮点数和一个整数。不同的文件可能具有完全不同的数据,并且一个文件中给定位置的数据可能与不同文件中相同位置的数据类型不同。然后,您有一堆不同的类需要使用这些不同的文件进行初始化,每个类都有自己的不同类型的不同数据成员的集合,这些数据成员来自文件中的不同位置。呸。

鉴于问题的复杂性,我的自然倾向是使解决方案尽可能简单。这里已经足够复杂了。我能想到的最简单的方法是为LineParser要解析的每种类型的文件简单地使用不同的具体类。但是,如果您有很多不同类型的文件,这将导致代码膨胀,并且随着数量的增长,维护起来会变得更加困难。因此,让我们继续假设您不想这样做。

然而,不会增加的一件事是文件中不同类型字段的数量。最终,实际上只有几个:字符串、整数、浮点数,也许还有一些其他特定于您的域的特殊内容。然而,即使您添加更多数据文件,字段类型的数量也将保持相对恒定。另一个常量是文件本身:它是字符数据。所以让我们利用它。

实现一些从文件存储类型(我在这里假设为字符数据)转换为不同字段的免费功能。如果您使用的是 Boost,则可以使用它lexical_cast来完成大部分工作。否则你可以使用stringstream或其他东西。这是一种可能的实现,还有很多其他实现:

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

现在我假设对于给定的Base-type 类,您知道您感兴趣的字段的位置和类型,并且这些是不变的。例如,对于Base代表股票报价的 a,您知道第一个字段是股票代码,它是一个字符串。

如果您的FileParser类所做的只是将所有内容从文件中提取出来并将其作为字符数据缓存在一个数组中,那么您的类可以是通用的,文件中的每个字段一个元素。同样,这里有许多可能的实现——我的重点是设计,而不是实际的代码。

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))
  {
  }
};

我喜欢这种方法有很多地方。一方面,尽管涉及到许多类和函数,但每一个都很简单。这里的每个小玩意儿都有一个明确定义的单一职责,并且不会尝试做太多事情。

另一方面,您的业务逻辑代码的自文档简洁明了,并且属于它:在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 回答