数组作为模板参数:stack或heap

Array as template parameter: stack or heap?

本文关键字:stack heap 参数 数组      更新时间:2023-10-16

与堆相比,我对堆栈的了解非常初级,但当涉及到数组时,据我所知,类似的东西是在堆栈上创建的

float x[100];

而像这样的东西是在堆上创建的

float* x = new float[100];

但是,如果我创建了一个模板数组类,并将其传递到"堆栈"数组类型(如float[100](中,会发生什么呢?示例:

#include <iostream>
using namespace std;
template <class T>
class Array {
public:
    int size;
    T* data;
    Array(int size_) : size(size_) {
        data = new T[size];
    }
    ~Array() {
        delete [] data;
    }
};
int main() {
    int m = 1000000;
    const int n = 100;
    Array<float[n]>* array = new Array<float[n]>(m);
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            array->data[i][j] = i * j;
    cout << array->data[10][9] << endl;
    delete array;
}

这里到底发生了什么?这个内存是在堆栈上创建的,还是在堆上创建的?我的猜测是堆,但这是怎么回事?编译器是否分配了一大块内存,然后存储每个n元素的索引指针?或者它会分配许多较小的内存块(不一定是连续的(,并存储指向每个块的指针吗?

此外,如果没有模板的帮助,我似乎无法做到这一点。具体来说,此代码不编译:

int m = 1000;
const int n = 100;
(float[n])* array = new (float[n])[m];

这是怎么回事?

编辑:

谢谢大家的语法提示。我真正感兴趣的是区块中发生了什么

int m = 1000;
const int n = 100;
float (*array)[n] = new float[m][n];

但我不知道如何在不使用模板的情况下编写它。我真正感兴趣的一件事是,如果编译器将其分配为堆上的一个大块,那么如何使用语法array[i][j]访问特定元素,而不存储指向第n个元素的指针?然后我意识到,由于n是常数,sizeof(float[n])是固定的,所以当你制作数组时,编译器会分配一个m元素的数组,其中每个元素都是float[n],在我的情况下是100 * 4 = 400字节。现在一切都有意义了。谢谢

Array<float[n]>* array = new Array<float[n]>(m);

这里发生的是两个堆分配。Array对象将在堆上分配,因为您使用了new来创建它。新表达式调用Array构造函数,后者再次使用new来分配数组data;因此CCD_ 14也被分配在堆上。

最好这样做:

Array<float[n]> array(m);

这会在堆栈上分配array(因此它将在块结束时自动销毁(。然而,当array对象本身在堆栈上时,数据仍然存储在堆上,因为它是在Array构造函数中在堆上分配的。这与具有std::vectorstd::string局部变量时发生的情况类似。

此外,如果没有模板的帮助,我似乎无法做到这一点。具体来说,此代码不编译:

这只是因为你的语法错误。正确的语法是:

float (*array)[n] = new float[m][n];

左侧显示了声明指向数组的指针的正确方法。对于右侧,您需要一个m float[n] s的数组。这表示为float[m][n];CCD_ 23在最后不进行。

您已经向后写入了数组扩展区。这项工作:

  int m = 1000;
  const int n = 100;
  float (*array)[n] = new float[m][n];
  delete[] array;

如果您想保持阵列扩展区的顺序相同,可以使用类型别名或适当的模板:

  using A = float[n];
  A* array = new A[m];

// at file scope
template<typename T, unsigned N> using add_extent = T[N];
// ...
  add_extent<float, n>* array = new add_extent<float, n>[m];

无论是在堆栈上还是在堆上,多维数组都被分配为m*n元素的单个块。当为指向数组类型(如float (*array)[n](的指针编制索引时,指针将按数组类型的步长一次递增n个元素。

所有内存都在堆上。编译器为数组分配一大块内存,并设置索引以使其可访问。

顺便说一句,如果有人复制或分配你的Array类,你会泄露内存和/或双重删除。

在行

Array<float[n]>* array = new Array<float[n]>(m);

在堆上分配了一个Array<T>的实例。你已经理解了这一点,给出了你关于一般分配的第一个声明。

也许令人困惑的部分是使用float[n]作为模板参数?

模板参数T(由Array定义中的关键字class表示(表示一种类型。它本身与任何形式的分配都无关。

为了证明这一点,让我们写一个简单的模板,它不使用任何参数:

#include <cassert>
using namespace std;
template <typename T>
class A {
};
int main(){
    A<float[100]> a1;
    A<float[1000]> a2;
    float f[100];
    assert(sizeof(a1) == sizeof(a2));
    cout << "a1 : " << sizeof(a1) << endl;
    cout.<< "f : " << sizeof(f) << endl;
}

输出:

a1 : 1
f : 400

所以这里的CCD_ 33确实是一个类型(1(

另一方面,当您使用关键字new时,您知道堆上正在分配一些内容。正如我所说,array变量将指向堆中的一个内存块。此外,模板本身包含一个堆分配(同样是关键字new(。

最后,我想详细说明new表示堆分配的基本前提。默认情况下,当在放置模式中使用时,实际分配很可能在堆栈上。


(1( 请注意,C++接受它,因为n被声明为常量,因此可以在编译时评估结果类型。删除n定义中的const特性,编译器就会抱怨。