我之前的问题(Programming model for classes with const variables)收到了一个完美的答案,但现在我有了新的要求,答案似乎不再适用了。
假设我有一个包含几个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。我尝试使用variadics的模板返回一个可变长度的元组,但它似乎并不重要。以下是我尝试过的修改后的助手类:
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
我无法解决这个问题,我可能正在寻找一些替代解决方案。++
答案 0 :(得分:2)
所以这变得有点复杂了。它也有点XY Problem,但至少在这里你明确了X和Y.
让我们从您提出的方法开始。这永远不会起作用:
std::get<i>(_t);
get
是函数模板,因此i
必须是积分常量表达式。换句话说,i
必须在编译时知道。
由于您提出的解决方案基本上是基于tuple
,因此当您无法制作i
和ICE时,整个事情会解开并崩溃。所以,让我们忘记提出的方法,再看看问题。你有一个包含大量东西的文件,大概是分开看起来像字段的东西。这些字段代表(据我所知)不同类型的数据。假设这是这样一个文件的一个例子:
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
类型类,您知道您感兴趣的字段的位置和类型,并且这些是不变的。例如,对于代表股票报价的Base
,您知道第一个字段是股票代码,它是一个字符串。
你的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拉出来。”干净。