【问题标题】:Storing a generic value and its type in C++: merge type+value in one class or separating them?在 C++ 中存储泛型值及其类型:将类型+值合并到一个类中还是将它们分开?
【发布时间】:2010-11-10 12:37:51
【问题描述】:

如果有一个当前可以预见一些简单数据类型的联合:

union Value
   {
   const char *str;
   double d;
   long l;
   short s;
   bool b;
   void *reference;
   };

联合还允许存储更复杂的数据类型(如枚举、对实例的引用等),但所有数据都存储在值联合的成员之一中。

由于数据类型可能很复杂,因此 Type 目前存储在一个单独的结构中,如下所示:

struct Type
   {
   short BaseType;            // one of TYPE_STRING, TYPE_LONG, TYPE_DOUBLE, ...
   char *referenceName;       // only used if BaseType==TYPE_REFERENCE
   Enumeration *enumeration;  // only used if BaseType==TYPE_ENUMERATION
   };

在重构应用程序时,我正在考虑将两个结构集成到一个类中。然后,Value 将成为纯接口 IValue,并且每种不同类型都有子类/实现。

将 Value 和 Type 结合起来的缺点是 Value 不能再简单地复制了,它总是需要被克隆。这样做的好处是保证你永远不会丢失值的类型。

如果我将 Value 和 Type 保留为单独的类,我仍然会将 Type 从一个结构更改为层次结构。 IType 将是接口,并且对于每种类型都会有一个子类/实现,例如StringType, DoubleType, LongType, ... 甚至 MyEnumType, MyOtherEnumType, ... IType 的实现可以决定在 Value union 中使用哪个值。

虽然复制类型仍然需要克隆,但我至少可以在没有克隆的情况下复制值。而且在大多数情况下,复制的值比类型多得多。

当保持 Value 和 Type 分开时,整个概念是您可以根据需要多次复制 Value,但如果您想获得实际的基础值,则需要 Type。

虽然应用程序是纯 C++(没有 .Net),但我愿意使用 .Net 作为我开发的灵感,而且我注意到在 .Net 中,您还可以轻松地在堆栈上创建值,通过对函数的价值,...无需克隆。

模板不是我的应用程序中的解决方案,因为这会使我大约 50% 的应用程序模板化。

问题:

  • 有使用 C++ 处理通用值和类型系统的经验吗?
  • .Net 是否具有可用作灵感的“通用”值和/或类型概念?
  • 还有更多关于将 Value 和 Type 保留在不同类中的论据吗?
  • 还有更多用于将 Value 和 Type 合并为一个类的参数吗?

【问题讨论】:

  • 你考虑过使用 boost::variant 吗?

标签: c++ types


【解决方案1】:

您应该使用 boost::variant 或 boost::any。这就是他们的目的。使用它们并不意味着您必须对整个解决方案进行模板化。此外,如果您不需要类型之间的动态运行时差异,那么这就是模板的用途。正确工作的正确工具 - 这是静态方差的模板,boost::variant 用于动态方差。您没有必要也没有理由推出自己的动态类型系统。

.NET 的设计失败是“对象”,但如果失败了,他们也可以使用 boost::variant 中使用的相同技术,据我所知,他们的泛型足够强大,可以做那种东西。

【讨论】:

  • 我也有模板相关的模板代码,但对于这种情况,我真的需要动态的运行时差异。感谢您提供提升链接。
  • 似乎 boost::any 解决了动态类型问题,但没有提供可扩展的类型系统。它仍然依赖于在开发类型中已知的类型(long、double、std::string 或您自己的类),但是如果我想将同一类的实例视为 boost::any 值中的不同类型怎么办?例如。我有一个 Book 类,但我想将小说和非小说视为单独的类型,并且我希望能够以与编写 any.type 相同的方式编写如下内容:any.type()==typeid(fiction) ()==typeid(int) 或 any.type()==typeid(Author)。
  • @Patrick:要使用类型系统,您可以从 Book 继承函数和非小说。或者您可以将 boost::any 与描述更精细区别的字符串 (?) 结合使用。
  • @Patrick:你会像往常一样使用继承。
  • @UncleBens&DeadMG:只有在编译时知道所有“类型”时,继承才有效。在我的例子中,类型可能只在运行时知道(例如,因为虚构和非虚构之间的区别在配置文件中指示)。
【解决方案2】:

听起来您正在尝试按照 VARIANT 的方式重新实现某些东西。你看过Boost::Any吗?这很好地解决了你的很多问题。但是,即使在那里,他们也确保类型信息随对象一起提供。你真的不应该像那样传递两个对象。他们需要结婚。

在模板方面,50% 的解决方案被模板化并没有什么问题。它可以很好地解决您面临的问题。模板化特征是一项了不起的发明:

template<typename T>
struct VarValue{
    VarValue(T _t) : m_T(_t){}
    typedef T value_type;
    T m_T;
};

然后 typedef 允许您无缝地使用包含的对象,而不必担心某些东西不知道它是什么变量类型。

【讨论】:

    【解决方案3】:

    我讨厌它们(因为内存管理麻烦),但是有 VARIANT 和更受欢迎的 CComVariant。 据我记得,类型和值在同一个结构中,VARIANT。

    您应该在 win sdk 和 ATL (CComVariant) 中找到它们。

    例如:

    struct tagVARIANT
        {
        union 
            {
            struct __tagVARIANT
                {
                VARTYPE vt;  // here is stored the type
                WORD wReserved1;
                WORD wReserved2;
                WORD wReserved3;
                union     // there the value
                    {
                    LONGLONG llVal;
                    LONG lVal;
                    BYTE bVal;
                    SHORT iVal;
                    FLOAT fltVal;
                    DOUBLE dblVal;
                    VARIANT_BOOL boolVal;
                    _VARIANT_BOOL bool;
                    SCODE scode;
                    CY cyVal;
                    DATE date;
                    BSTR bstrVal;
                    IUnknown *punkVal;
                    IDispatch *pdispVal;
                    SAFEARRAY *parray;
                    BYTE *pbVal;
                    SHORT *piVal;
                    LONG *plVal;
                    LONGLONG *pllVal;
                    FLOAT *pfltVal;
                    DOUBLE *pdblVal;
                    VARIANT_BOOL *pboolVal;
                    _VARIANT_BOOL *pbool;
                    SCODE *pscode;
                    CY *pcyVal;
                    DATE *pdate;
                    BSTR *pbstrVal;
                    IUnknown **ppunkVal;
                    IDispatch **ppdispVal;
                    SAFEARRAY **pparray;
                    VARIANT *pvarVal;
                    PVOID byref;
                    CHAR cVal;
                    USHORT uiVal;
                    ULONG ulVal;
                    ULONGLONG ullVal;
                    INT intVal;
                    UINT uintVal;
                    DECIMAL *pdecVal;
                    CHAR *pcVal;
                    USHORT *puiVal;
                    ULONG *pulVal;
                    ULONGLONG *pullVal;
                    INT *pintVal;
                    UINT *puintVal;
                    struct __tagBRECORD
                        {
                        PVOID pvRecord;
                        IRecordInfo *pRecInfo;
                        }   __VARIANT_NAME_4;
                    }   __VARIANT_NAME_3;
                }   __VARIANT_NAME_2;
            DECIMAL decVal;
            }   __VARIANT_NAME_1;
        } ;
    

    【讨论】:

    • 为什么是 -1 ?这是 MS 以这种方式实现的,wheaties 正在寻找一些灵感来源。
    • @Stephane Rolland 我是?我没有对你投反对票,也不明白你为什么得到-1。
    • @Stephane,+1 因为这是 MS 领域的有效解决方案...我猜有人对 COM 有异议! ;) boost::variant 界面要好得多...
    • 不知道谁对你投了反对票,但我再次对你投了赞成票,因为你的回答很有用,虽然我不想用。
    猜你喜欢
    • 1970-01-01
    • 2011-04-16
    • 2021-10-22
    • 2012-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-07
    • 2020-08-24
    相关资源
    最近更新 更多