这是另一个解决方案:
#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& 或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 和另一个some 之间安全地复制,而无需指定类型。存储的大数据可以通过引用访问,也可以直接修改,但是需要类型。例如
string& shipName = settings["shipName"];
将在 settings 容器中启用像 shipName += ... 这样的修改。
将指针或 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 包含一个函数指针。
使用自定义数据类型和自定义目的地,可以将相同的想法应用于构建,例如延迟函数调用的消息队列。