我的系统非常“重”(大量代码),但速度非常快,并且功能非常丰富(跨平台 C++)。我不确定你想在设计上走多远,但这是我所做的最重要的部分:
DatumState
- 持有“类型”的“枚举”的类,加上本机值,这是所有原始类型之间的“联合”,包括void*
. 此类与所有类型解耦,可用于任何本机/原始类型和“引用”void*
类型。由于 " enum
" 也有 " VALUE_OF
" 和 " REF_TO
" 上下文,因此此类可以呈现为“完全包含” a float
(或某些原始类型),或“引用但不拥有” a float
(或某些原始类型)。(我实际上有“ VALUE_OF
”、“ REF_TO
”和“ PTR_TO
”上下文,所以我可以在逻辑上存储一个值,一个不能为空的引用,
Datum
- 完全包含 a 的类DatumState
,但它扩展了其接口以适应各种“知名”类型(如MyDate
、MyColor
、MyFileName
等)。这些知名类型实际上存储在成员void*
内部DatumState
。但是,由于“ enum
”部分DatumState
具有“ VALUE_OF
”和“ REF_TO
”上下文,它可以表示“ pointer-to-MyDate
”或“ value-of-MyDate
”。
DatumStateHandle
- 使用(众所周知的)类型(如MyDate
, MyColor
,MyFileName
等)参数化的帮助模板类。这是用于Datum
从众所周知的类型中提取状态的访问器。默认实现适用于大多数类,但任何具有特定访问语义的类只会覆盖其特定模板参数化/实现,用于该模板类中的一个或多个成员函数。
Macros, helper functions, and some other supporting stuff
Datum
- 为了简化在我的/中“添加”众所周知的类型Variant
,我发现将逻辑集中到几个宏中很方便,提供一些支持功能,如运算符重载,并在我的代码中建立一些其他约定。
作为这个实现的“副作用”,我得到了很多好处,包括引用和值语义、所有类型的“null”选项以及对所有类型的异构容器的支持。
例如,您可以创建一组整数并为其编制索引:
int my_ints[10];
Datum d(my_ints, 10/*count*/);
for(long i = 0; i < d.count(); ++i)
{
d[i] = i;
}
类似地,某些数据类型由字符串或枚举索引:
MyDate my_date = MyDate::GetDateToday();
Datum d(my_date);
cout << d["DAY_OF_WEEK"] << endl;
cout << d[MyDate::DAY_OF_WEEK] << endl; // alternative
我可以存储项目集(本机)或集Datum
(包装每个项目)。对于任何一种情况,我都可以递归地“解包”:
MyDate my_dates[10];
Datum d(my_dates, 10/*count*/);
for(long i = 0; i < d.count(); ++i)
{
cout << d[i][MyDate::DAY_OF_WEEK] << endl;
}
有人可能会争辩说我的 " REF_TO
" 和 " VALUE_OF
" 语义是矫枉过正的,但它们对于 "set-unwrapping" 是必不可少的。
我已经Variant
用九种不同的设计完成了这个“”的事情,我目前是“最重的”(大多数代码),但我最喜欢的一个(几乎是最快的,对象占用空间很小),我已经弃用了其他八种设计供我使用。
我的设计的“缺点”是:
- 对象是通过
static_cast<>()
a访问的void*
(类型安全且相当快,但需要间接;但是,副作用是设计支持“ null
”的存储。)
Datum
由于通过接口公开了众所周知的类型,因此编译时间更长(但DatumState
如果您不想要众所周知的类型 API,您可以使用)。
无论您的设计如何,我都会推荐以下内容:
使用“ enum
”或其他东西来告诉您“类型”,与“值”分开。(我知道您可以将它们压缩成一个“ int
”或带有位包装的东西,但是随着新类型的引入,访问速度很慢并且维护起来非常棘手。)
依靠模板或其他东西来集中操作,使用特定类型(覆盖)处理的机制(假设您要处理非平凡类型)。
游戏的名称是“添加新类型时的简化维护”(或者至少对我来说是这样)。就像一篇好的学期论文一样,如果你重写、重写、重写,以保持或增加你的功能,因为你不断删除维护系统所需的代码(例如,最大限度地减少适应新类型所需的工作量)到您现有的Variant
基础设施)。
祝你好运!