我正在尝试编写通用设置管理器。设置来自INI文件,可以是整数或字符串。我希望能够存储这些设置的向量,因此我可以遍历它们以找到所需的值,并提取其值。
我希望能够写出这样的内容:
// Desired usage:
Settings settings; // A container class, defined below
settings.add(new Setting<string>("shipName", "HAL"));
settings.add(new Setting<int> ("shipYear", 2001));
// Different return types:
string shipName = settings.getSetting("shipName")->getValue();
int shipYear = settings.getSetting("shipYear")->getValue();
我有3节课:
AbstractSetting,它是所有设置类的母亲。我需要这个,所以我可以在矢量中存储一致的类型。
设置,一个继承自AbstractSetting的模板化类。在这里,我可以将设置数据存储为字符串或整数。
设置,一个用于保存我的设置的容器类,负责存储和检索。
方法主要是getter / setter。由于实现很明显,为了简洁起见,我省略了它们。
我的问题是我在AbstractSetting中放置什么来允许我为getValue()提供不同的实现(具有不同的返回类型)?
class AbstractSetting
{
private:
string mName;
public:
AbstractSetting(const string &name); // Constructor
// What does here? Need to declare getValue somehow
};
////////////////////////////////////////
// Sublcasses of AbstractSetting, one for each T
template <class T>
class Setting : public AbstractSetting
{
private:
T mValue;
public:
Setting<T>(const string &name, const T &value);
void setValue(const T &value);
T getValue();
};
////////////////////////////////////////
// Container for all our settings
class Settings
{
private:
Vector<AbstractSetting *> mSettings;
public:
const AbstractSetting *getSetting(const string &name) const;
void add(AbstractSetting *setting); // Store new setting
};
答案 0 :(得分:2)
我认为你必须告诉编译器你期望什么类型,你可以这样做:
class AbstractSetting
{
private:
string mName;
public:
AbstractSetting(const string &name); // Constructor
template <typename T>
T& getTheValue()
{
Settings<T>* upcast = dynamic_cast<Settings<T>*>(this);
if (!upcast)
; //throw your exception
return upcast->getValue();
}
template <typename T>
T const& getTheValue() const
{
Settings<T>* upcast = dynamic_cast<Settings<T>*>(this);
if (!upcast)
; //throw your exception
return upcast->getValue();
}
};
并用:
调用它 string & value = settings.getSettings("strName").getTheValue<string>();
int otherValue = settings.getSettings("intName").getTheValue<int>();
如果您不想指定返回类型,可以通过引用传递变量,方法如下:
class AbstractSetting
{
private:
string mName;
public:
AbstractSetting(const string &name); // Constructor
template <typename T>
void getTheValue(T& ret)
{
Settings<T>* upcast = dynamic_cast<Settings<T>*>(this);
if (!upcast)
; //throw your exception
ret = upcast->getValue();
}
};
并使用它:
string value;
int otherValue;
settings.getSettings("stringName").getTheValue(value); // will do a copy, so this is kind of bad, avoid this by using pointer.
settings.getSettings("intName").getTheValue(otherValue);
答案 1 :(得分:1)
我会提供另外一个我找到的解决方案,不过我还不确定我是否喜欢它...这是改编自: Why can't C++ deduce template type from assignment?
将其添加到AbstractSettings类:
template<class T>
operator T()
{
return getValue<T>();
}
我可以覆盖C ++的隐式转换并使这种语法有效:
int shipYear = *settings.getSetting("shipYear");
答案 2 :(得分:0)
这是另一个解决方案:
#include <iostream>
#include <map>
#include "some.hpp"
int main(int argc, char *argv[])
{
using namespace std;
map <string, some> settings;
settings["shipName"] = string("HAL");
settings["shipYear"] = 2001;
string shipName = settings["shipName"];
int shipYear = settings["shipYear"];
cout << shipName << " " << shipYear << endl;
}
小心,必须使用与存储时完全相同的类型检索数据。这就是我使用string("HAL")
的原因;普通"HAL"
const char*
需要shipName
。
此解决方案的关键是类ivl::some
,它是库ivl的一部分,其工作方式与boost::any类似,但在数据可以适应的情况下更有效地使用堆栈预定的大小。
实际上,boost::any
使用的方法与您的方法大致相同,始终将数据放在堆上。它的源代码非常小,与您的解决方案更相关,所以它也可能有所帮助。
some
使用单个指向函数来支持复制或删除操作,而不是使用非模板基类以及带有虚方法的模板派生类,而不是第二个参数是否为零。
因为此函数是静态模板方法的实例化,所以数据类型在函数体内是已知的,以及是否使用堆或堆栈内存。这会影响分配(new
或展示位置new
)和取消分配(delete
或普通析构函数调用)。该决定完全基于数据的大小。
数据访问只需static_cast
完成,并要求用户通过方法_<T>()
明确指定类型,或通过转换运算符隐式指定T&
或{{1} },其中自动推导出模板参数const T&
。这些与前面的答案类似,但通过T
转换也支持读写访问。
这个示例的T&
的精简版本需要100行代码:
some.hpp
可以在一个//-----------------------------------------------------------------------------
template <typename T> void* away(T* p) { return static_cast <void*> (p); }
template <typename T> const void* away(const T* p) { return static_cast <const void*>(p); }
template <typename T> void* ref (T& r) { return away(&r); }
template <typename T> const void* ref (const T& r) { return away(&r); }
template <typename T> T* back (void* p) { return static_cast <T*> (p); }
template <typename T> const T* back (const void* p) { return static_cast <const T*>(p); }
template <typename T> T& deref(void* p) { return *back <T>(p); }
template <typename T> const T& deref(const void* p) { return *back <T>(p); }
inline void* peek(void* p) { return deref <void*> (p); }
inline const void* peek(const void* p) { return deref <const void*>(p); }
//-----------------------------------------------------------------------------
enum { stack_size = 8 };
template <int N = stack_size>
class some_
{
union { char b[N]; void* p; }; // buffer; pointer
void (*f)(void*&, const void*); // operations
//-----------------------------------------------------------------------------
template <typename T>
static void stack(void*& dest, const void* src)
{
if (src) new (dest) T(deref <T>(src));
else back <T>(ref(dest))->~T();
};
template <typename T>
static void heap(void*& dest, const void* src)
{
if (src) dest = new T(deref <T>(peek(src)));
else delete back <T>(dest);
};
//-----------------------------------------------------------------------------
template <typename T> bool fits() { return sizeof(T) <= N; }
void read() { f = 0; }
template <typename T>
void read(const T& v)
{
fits <T>() ? new (b) T(v) : p = new T(v);
f = fits <T>() ? stack <T> : heap <T>;
}
void read(const some_& s) { if ((f = s.f)) (*f)(p = b, s.b); }
void free() { if ( f ) (*f)(p, 0); }
template <typename T> void* ptr() { return fits <T>() ? away(b) : p; }
template <typename T> const void* ptr() const { return fits <T>() ? away(b) : p; }
protected:
//-----------------------------------------------------------------------------
some_& assign() { free(); read(); return *this; }
template <typename T> some_& assign(const T& v) { free(); read(v); return *this; }
public:
//-----------------------------------------------------------------------------
some_() { read(); }
~some_() { free(); }
some_(const some_& s) { read(s); }
template <typename T> some_(const T& v) { read(v); }
template <typename T> some_(const T v[]) { read <const T*>(v); }
some_& operator=(const some_& s) { return assign(s); }
template <typename T> some_& operator=(const T& v) { return assign(v); }
template <typename T> some_& operator=(const T v[]) { return assign <const T*>(v); }
some_& init() { return assign(); }
some_& clear() { return assign(); }
bool empty() const { return f == 0; }
bool operator()() const { return f != 0; }
template <typename T> T* to() { return back <T>(ptr <T>()); }
template <typename T> const T* to() const { return back <T>(ptr <T>()); }
template <typename T> T& _() { return *to <T>(); }
template <typename T> const T& _() const { return *to <T>(); }
template <typename T> operator T&() { return _<T>(); }
template <typename T> operator const T&() const { return _<T>(); }
};
//-----------------------------------------------------------------------------
typedef some_<> some;
//-----------------------------------------------------------------------------
和另一个some
之间安全地复制数据,而无需指定类型。可以通过引用访问大型存储数据或直接修改,但是需要类型。 E.g。
string& shipName = settings["shipName"];
会在shipName += ...
容器中启用settings
之类的修改。
将指针或C数组分配给some
仅复制指针,而不是实际数据。 C数组存储为指针,因此必须在检索时指定相应的指针类型。
clear()
some
是安全的,即使它存在于堆栈中,也可以正确解析存储的对象,并且可以知道它是否为empty()
。这些操作不需要任何类型。
可以控制存储数据的大小,但要统一。例如,如果您愿意为每个存储的项目提供32个字节并在堆上仅为较大的项目分配空间,则可以定义
typedef some_<32> some;
默认值为8个字节,与联合中的指针共享。这是64位的最小可能性。对于零大小的类型,将需要专用的仅堆栈版本的some
。 some
还包含一个指向函数的指针,因此它的大小通常为16个字节。
some
的嵌套容器是可能的,例如,异构数据树。
完整版支持测试给定类型是否成功,或者是否例如两种存储类型相同,使用特殊类型的type_id
。但是无法自动恢复类型或进行转换。
更高级的结构some_of
在检索时根本不需要指定数据类型,但仅适用于数据的指定目标类型,例如: ostream
。这可以这样工作:
map <string, some_of <ostream> > settings;
settings["shipName"] = string("HAL");
settings["shipYear"] = 2001;
cout << settings["shipName"] << " " << settings["shipYear"] << endl;
为实现这一目标,some_of
还包含一个指向函数的指针。
使用自定义数据类型和自定义目标,可以将相同的想法应用于构建,例如延迟函数调用的消息队列。