根据我在动态语言方面的背景,我发现在使用静态类型语言(如C ++)表达我的意图时遇到了问题。
我正在为我的应用程序设计一个首选项系统。因为每个首选项都会有一些相关的值(默认值,限制,观察者函数......)我决定将每个首选项封装在它自己的对象中。这是我的初稿:
class Preference // purely abstract class
{
parseFromString(String s) = 0;
get() = 0;
void set(newVal) = 0;
private:
// internal data
};
现在我需要创建一些派生类,例如IntPreference
,FloatPreference
和StringPreference
。以下是他们的声明的样子:
class IntPreference : Preference class StringPreference : Preference
{ {
int parseFromString(String s); String parseFromString(String s);
void set(int newVal); void set(String newVal);
// etc. // etc.
} }
既然set()
方法在int
类中使用IntPreference
参数,在String
中使用StringPreference
参数,则无法声明此函数在基类中。 parseFromString()
的返回值也是如此。我理解这在C ++中是不可能的,因为派生类中具有相同名称和不同参数类型的函数只是掩盖,而不是覆盖它们的祖先。再一次,这就是我用动态语言表达自己的方式,C ++中正确的模式是什么?
编辑:抱歉,我忘了提到我需要一个基类来将它们全部存储在哈希表中:
Hash(const char *name, Preference pref);
答案 0 :(得分:3)
你现在拥有的是一个糟糕的boost::any
课程,你可能应该这样做
只是使用它。
您的parseFromString()
会员功能可疑。你用的是
动态类型来决定从字符串中解析什么,这是什么
总是必须静态地知道。
class my_any {
public:
template<typename T>
explicit // don't rely on conversions too much
my_any(const T& t) : x_(t) {}
// might throw if the cast fails
template<typename T>
T& get() { return boost::any_cast<T&>(x_); }
// also maybe move semantics
template<typename T>
set(const T& t) { x_ = t; }
private:
boost::any x_;
};
// usage:
my_any m;
m.set(23);
try {
int& x = m.get<int>();
catch(boost::bad_any_cast& ex) {
// ...
}
// for setting things from string just do
// the right thing at the call site of set
如果你不喜欢模板,你可以简单地提供一些默认值:
my_any::getInt(); my_any::getString();
编辑:如果boost::any
过于通用,您想要限制
你的构造使用了一组特定的值
boost::variant
。虽然变体对编译有更大的影响
时间,对于初学者来说可能很难使用。
EDIT2 :哈希表问题:
typedef boost::unordered_map<std::string, my_any> preference_table;
preference_table t;
// i added a template constructor to my_any
t.insert(std::make_pair("Foobar", my_any(23)));
答案 1 :(得分:2)
哇 - 慢下来!您有强类型语言。这不是一个设计缺陷,它是故意的:它意味着有点限制,因此它可以对程序的正确性进行编译时检查,并生成更快,更清晰的代码。请不要通过创建一些类型模糊的界面来抛弃类型安全!您的问题中没有任何内容表明您有任何需要这样做。
考虑做以下事情:
struct Config
{
int max_for_whatever_;
string name_for_whatever_;
double scaling_factor_for_whatever_else_;
bool verbose_;
};
在解析输入时,您可以填充特定的相关成员变量。
现在,有很多好的库可以做到这一点。领先的通用C ++第三方库是“boost”,它具有参数解析功能。虽然一些C ++编译器附带了增强版本(具体来说,GNU C ++编译器有一个扩展的getopt
支持“长”选项格式,用于命令行参数,如“--flag”而不仅仅是“-f”) ,经过尝试和信任的UNIX / Linux工具getopt()
可以用作:
int c;
Config config = { 20, "plugins", 29.3, true };
while ((c = getopt(argc, argv, "m:n:s:v")) != EOF)
{
switch (c)
{
case 'm': config.max_for_whatever_ = lexical_cast<int>(optarg); break;
case 'n': config.name_for_whatever_ = optarg; break;
case 'd': config.scaling_factor_for_whatever_ = lexical_cast<double>(optarg); break;
case 'v': config.verbose_ ^= true; break;
default:
std::cerr << argv[0] << ": unsupported option '" << c << "' - exiting\n";
exit(EXIT_FAILURE);
}
}
// then, use the configuration parameters directly by name...
无论您是从配置文件,命令行参数,某种注册表方式中读取,概念都是相同的:当您遇到特定的配置值时,请尝试将它们写入特定于其导入的正确类型和命名变量中代码。
答案 2 :(得分:1)
就我个人而言,我不会为每一件事创造单独的分类。它们不是可互换的,有时候你不能给出一个想要StringPreference的IntPreference ......如果你把一个抽象的“Preference”传递给一个函数,它会期望它是一个特定的类型,以便利用这些数据
我不会在这里创建子类,我会有一个Preference类,它有单独的函数getIntValue(),getStringValue()等。
答案 3 :(得分:1)
如果您的偏好设置具有不同类型的值,我认为您不能拥有单一界面。这就是我想到的:
class IPreference
{
public:
virtual ~IPreference() {};
virtual void Parse( std::istream& s ) = 0;
virtual void Serialize( std::ostream& s ) = 0;
};
template <typename T>
class Preference : public IPreference
{
public:
const T& Get() const { return m_value; }
void Set(const T& value) const { m_value = value; }
private:
T m_value;
};
我会尝试尽可能多地在基类中加入逻辑。但是如果你想避免类型轮询,我认为你可能需要每个属性类型一个类。
再想一想,您可能会认为所有属性类型都支持读取/写入std :: streams。然后你可以使用模板并使用stringstream(如果你从字符串中读取)。
答案 4 :(得分:1)
您想使用模板:
template <typename T>
class ChildPreference
{
T parseFromString(std::string s) {
//todo
}
void set(T newVal) {
//todo
}
// etc.
}
ChildPreference<int> intObj;
ChildPreference<float> fltObj;
ChildPreference<std::string> strObj;
请注意模板,您必须定义功能。
答案 5 :(得分:1)
如果Base声明了一个成员函数集(int x),并且Derived声明了一个成员函数集(字符串c)(同名但不同的参数类型和/或const),那么Base set(int x)是“隐藏”而不是“重载”或“覆盖”(即使Base set(int x)是虚拟的)
答案 6 :(得分:0)
你可以使用模板,正如Jacob S.的评论中所建议的那样。它看起来像这样:
template<class T>
class Preference
{
public:
parseFromString(std::string s) = 0;
T get() { /* some implementation */ }
void set(T newValue) { /* some implementation */ }
private:
T value_;
};
您可以使用它:
Preference<int> intPrefs;
Preference<std::string> stringPrefs;