有没有一种简单有效的方法可以在达到数据类型限制后动态更改数据类型?

Is there an easy and efficient way to dynamically change a data type once that data type's limit has been reached?

本文关键字:数据类型 动态 一种 简单 方法 有效 有没有      更新时间:2023-10-16

一旦达到数据类型的限制,我想增加该数据类型的大小。例如,假设我有一个类:

struct Counter {
    unsigned short x;
    void IncrementCount() { x++; }
};

一旦我更改了Counter.x的值,我希望它将x提升为无符号int,而不是无符号short。

int main() {
    Counter c;
    c.x = ~0;  // c.x is max unsigned short value 2^16
    c.IncrementCount();     // c.x is now an unsigned int with value 2^16 + 1, NOT an unsigned short with value 0
}

我这样做的原因是为了在Counter类中存储尽可能少的内存。

显然,我希望尽可能减少对性能和可读性的影响。

我已经考虑过使用一个void指针并强制转换到适当的数据类型(但我如何在不使用额外内存的情况下跟踪该类型?(,更改Counter类上的IncrementCount((方法,并在达到无符号short的极限时创建一个新的Counter对象(使用int而不是short((但这会增加一些复杂性,并且在每次增量时都会增加一个额外的if(。也许是一个位图,每当它需要一个额外的位时,它的大小就会增加?仍然增加了复杂性和一些性能打击。

如果我有1000万个计数器,我不想为int使用额外的2个字节(因为这需要2000万个额外的字节(。我知道对齐可能会解决short->int的问题,但我希望这也适用于int32->int64和其他类型。同样值得注意的是,这是一个运行时问题(我不知道编译时的大小(。

C++中有没有更简单的方法可以做到这一点?

C和C++中的数据类型必须在编译时完全定义。你不可能有一个短变量然后被提升为int.

Python等编程语言将数据类型附加到值,而不是变量。这就是你可以做的原因:

a = 1
a = "hi"

因为数据类型附加到值1,然后附加到值"hi"。

为此付出的代价是,"a"的记账通常很高,至少有一个指针指向动态分配的内存块,记账用于动态内存分配,并且值具有数据类型判别机制来获得数据类型的值。这种机制允许以较低的运行时效率为代价在运行时推断数据类型。

至少有两种方法可以实现这一点。从历史上看,这是作为一种变体数据类型完成的。请参阅此处了解更多信息。另一种选择是以面向对象的方式进行,其中有一个包含所有可能操作的基类object。这或多或少是Python所做的,但在C中使用C++和函数指针。例如:

class Object {
public:
  virtual Object_ptr add(Object_ptr other) = 0;
};

例如,int的+运算是通常的算术加法,其中2+2=4,而字符串的+运算则是串联运算,其中"Hello"+"World"="Hello World"。遵循我们可以做的相同逻辑:

class IntObject {
public:
  virtual Object_ptr add(Object_ptr other) {
    if (other->dataType() == DATATYPE_INT) {
      return new IntObject(this->value + other->value);
    } else {
      raiseError("dataTypeError");
    }
  }
};

Python还具有任意大小的长整数的良好特性。你可以有一个值,它的位数和你的内存一样多。例如在Python中:

>>> 1<<128
340282366920938463463374607431768211456L

在内部,Python检测到该数字不适合int32/int64,并将在运行时升级数据类型。这或多或少就是Python在内部所做的:

class IntObject {
public:
  virtual Object_ptr add(Object_ptr other) {
    if (other->dataType() == DATATYPE_INT) {
      if (operationWillOverflow(other)) {
        auto a = new LongIntObject(this);
        auto b = new LongIntObject(other);
        return a.add(b);
      } else {
        return new IntObject(this->value + other->value);
      }
    } else {
      raiseError("dataTypeError");
    }
  }
};

唯一的方法就是,正如您所说,检查每个增量的值是否符合当前类型,如果不符合,则使用uint32而不是uint16创建新的obj,并复制旧的值。但是,在这种情况下,如果对象是多态的,则必须包含一个指向虚拟表函数的指针,该函数需要额外的8字节内存。

当然,你可以考虑将计数存储在不同的数据结构中,而不仅仅是一个基本类型,但它们很可能包含一些额外的信息,需要更多的内存(但内存对你来说至关重要(,所以这不是一个选择。此外,你不能处理免费存储,因为指针是32-64位(取决于机器(,这与存储uint64相同。

所有这些的重点是,你不能避免复制,因为在为向量重新分配内存时,你必须复制。你永远不能现在位于你的计数器旁边的内存->现在的方式只是"调整大小"一个类型。

如何计算计数器编号对于当前类型太大,需要新类型?非常简单,只需检查x+1值是否等于0。如果发生这种情况->发生溢出->当前类型太小。

总之,复制是"调整"对象大小的唯一方法。但是,如果您同意这样做,您将需要编写一些用于检查和复制的代码,而且您也无法避免每次增量的值检查开销。

C++是一种静态类型的语言。每个数据结构都有一个无法更改的编译时大小。

您可以让类分配更多内存。但这是一个独立于类自身存储的分配(正如注释中所指出的,如果不比int多,那么它的内存成本也一样高(。一个特定的C++类将只具有单一的大小。

查看2000万值的数据存储策略。有两种基本方法:

如果将对象存储在阵列中,则其大小和类型将设置为石头。这是尼科尔的回答。这是评论中建议的:使用所需的最大整数类型并完成。

如果存储指向对象的指针,则可以自由地将指向的对象替换为具有较大值空间的另一类型。但是,从一开始就有一个内置的内存开销,每个值可能有两个指针的开销(第二个指针是多态对象中隐藏的vtable指针(。这没有道理。

如果您想更改同一对象的基础整数类型,则必须动态分配整数,并在对象中保持指向该内存的指针;您只是将指针(这次是指向不同大小的整数对象(移动到对象中,具有相同的开销,加上堆管理的内存开销,这对于像这里这样的小块来说是占主导地位的。