这是另一个解决方案:
#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
该解决方案的关键是 class ivl::some
,它是库ivl的一部分,其工作方式与boost::any类似,但在数据可以适合预定大小时更有效地使用堆栈。
事实上,boost::any
使用或多或少与您相同的方法,总是将数据放在堆上。它的源代码非常小,并且与您的解决方案更相关,因此它也可能有所帮助。
不是使用非模板基类和带有虚拟方法的模板派生类,而是使用some
一个指向支持复制或删除操作的函数的指针,具体取决于第二个参数是否为零。
因为这个函数是一个静态模板方法的实例化,所以数据类型在函数体中是已知的,以及是否使用堆或堆栈内存。这会影响分配(new
或放置new
)和取消分配(delete
或普通析构函数调用)。该决定完全基于数据的大小。
数据访问由简单地完成static_cast
并要求用户指定类型,或者通过 method 显式指定_<T>()
,或者通过转换运算符隐式指定到T&
or ,其中自动推导出const T&
模板参数。这些与前面的答案类似,但通过转换T
也支持读写访问。T&
对于这个例子来说,一个精简的版本some.hpp
就足够了,需要 100 行代码:
//-----------------------------------------------------------------------------
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
和另一个之间安全地复制,而无需指定类型。存储的大数据可以通过引用访问,也可以直接修改,但是需要类型。例如
string& shipName = settings["shipName"];
将启用容器shipName += ...
内的修改。settings
将指针或 C 数组分配给 asome
只会复制指针,而不是实际数据。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
包含一个更多的函数指针。
使用自定义数据类型和自定义目的地,同样的想法可以应用于构建例如用于延迟函数调用的消息队列。