当前位置: 首页 > 知识库问答 >
问题:

为什么std::initializer\u list可以不指定大小而同时分配堆栈?

杜辰龙
2023-03-14

我从这里了解到,std::initializer\u list不需要分配堆内存。这对我来说很奇怪,因为您可以在不指定大小的情况下接受std::initializer\u list对象,而对于数组,您总是需要指定大小。尽管初始值设定项在内部的列表与数组几乎相同(正如帖子所建议的)。

我很难理解的是,使用C作为静态类型语言,每个对象的内存布局(和大小)必须在编译时固定。因此,每个std::array都是另一种类型,我们只是从一个公共模板派生这些类型。但是对于std::initializer\u list,这个规则显然不适用,因为接收函数或构造函数不需要考虑内存布局(虽然它可以从传递给其构造函数的参数派生)。只有当类型堆分配内存并且只保留存储来管理该内存时,这对我来说才有意义。那么,区别就很像std::array和std::vector,后者也不需要指定大小。

然而std::initializer\u list不使用堆分配,正如我的测试所示:

#include <string>
#include <iostream>

void* operator new(size_t size)
{
    std::cout << "new overload called" << std::endl;    
    return malloc(size);
}


template <typename T>
void foo(std::initializer_list<T> args)
{
    for (auto&& a : args)
    std::cout << a << std::endl;
}

int main()
{
    foo({2, 3, 2, 6, 7});

    // std::string test_alloc = "some string longer than std::string SSO";
}

这怎么可能呢?我可以为自己的类型编写类似的实现吗?这真的可以让我在演奏编译时管弦乐队时免于搞砸我的二进制文件

编辑:我应该注意到,我想问的问题不是编译器如何知道它应该用什么大小实例化初始化器列表(可以通过模板参数推导来实现),而是它如何与初始化器列表的所有其他实例化不是不同的类型(因此为什么您可以将不同大小的初始化器列表传递给同一个函数)。

共有3个答案

谢涵煦
2023-03-14

再加上我自己的一些随机思考,std::initializer\u list实际上是一种有趣的动物-它是一种嵌合体,部分STL,部分编译器构造。

如果查看适当的STL头文件,您将找到定义API的定义。但实际上,实际的实现是内置在编译器中的,所以在编写时,可以说:

std::initializer_list <int> l = { 1, 2, 3, 4, 5 };

编译器说“啊哈!一个初始化列表(和一个int的列表,要启动),我知道那是什么,我会构造一个”。确实如此。STL本身没有这样做的代码。

换句话说,对于编译器来说,std::initializer\u list在某种程度上是一种本机类型。只是它不是,不完全,因此它是独一无二的。

宰父浩漫
2023-03-14

问题是,std::initializer\u list不会将对象保存在其内部。当您实例化它时,编译器会注入一些额外的代码来在堆栈上创建一个临时数组,并将指向该数组的指针存储在initializer\u列表中。就其价值而言,initializer\u list只不过是一个具有两个指针(或一个指针和一个大小)的结构:

template <class T>
class initializer_list {
private:
  T* begin_;
  T* end_;
public:
  size_t size() const { return end_ - begin_; }
  T const* begin() const { return begin_; }
  T const* end() const { return end_; }

  // ...
};

当你这样做时:

foo({2, 3, 4, 5, 6});

从概念上讲,发生的情况如下:

int __tmp_arr[5] {2, 3, 4, 5, 6};
foo(std::initializer_list{arr, arr + 5});

一个小的区别是,数组的生命周期不超过initializer_list的生命周期。

洪胜涝
2023-03-14

...而对于数组,您总是需要指定大小...

你是说喜欢

int a[] = {2, 3, 2, 6, 7};

我很难理解的是,使用C作为静态类型语言,每个的内存布局(和大小)必须在编译时固定。

初始化列表的大小在编译时与上面数组的大小一样固定——它是固定的,因为您在编译前显式编写了带括号的表达式{2, 3, 2, 6, 7}

这怎么可能?我可以为自己的类型编写类似的实现吗?

不能截获带括号的init列表的解析。正如您所看到的,处理列表初始化的规则非常具体。

但是,std::initializer_list旨在轻量级,因此您可以直接使用它。正如另一个答案所说,您可以将其视为一个正常的隐式大小的数组,并隐式转换为类似范围的视图。

 类似资料:
  • 我们将和设置为1536m。现在,如果我理解正确的话,j-xmx表示堆的最大大小。 本系统采用4核15GB ram进程。 但是,当我检查正在运行的Java进程的RSS(使用top)时,我看到它使用的值比大,大约。 对于来说,有4个内核和15GB RAM的理想设置是什么(假设除了Java应用程序之外,系统中没有其他进程在运行)

  • 我一直想知道为什么STL优先级队列默认使用最大堆而不是最小堆。我想到的两个明显的用例是寻路(Dijkstra)和构建霍夫曼代码。这两种算法都需要首先拉取最小元素。由于排序(std::sort)默认使用升序,我想知道priority_queue背后的设计原因是什么,因为我非常喜欢默认的最小堆。

  • 为什么在堆上分配内存,而没有? 它们之间的主要区别是可以重用,而std::latch不能重用,但我找不到解释为什么前者会分配内存。

  • 我已经在eclipse中安装了以及用于打开IBM format堆转储的插件。 当我试图从eclipse中用打开堆转储时,我得到一个消息框错误,它说: “从'C:\UserData\heapdump.44124802.212242.6876.0003.phd'解析堆转储”过程中出现内部错误。Java堆空间

  • 我们在日志中看到了OutOfMemoryExceptions,它们似乎与java堆提交大小从~1G增长到~2.4G一致。尽管有错误消息,但堆空间似乎没有用完。除了抛出异常(和生成的堆转储)之外,调整大小似乎最终会成功,应用程序继续运行,没有任何问题(堆提交大小约2.4G)。 下面是日志输出的一个示例: 请注意,在OOME之前,提交的堆总数在1GB和2.4GB之间振荡。我们可以看到,它之前非常稳定在

  • 我试图用C(11/14)实现fortran的重塑功能,并设计了一个函数。此函数接受两个std::initializer\u列表。第一个initializer\u列表给出了我用来初始化多维数组的初始值。第二个initializer\u列表给出了数组每个维度的大小。我写了一份这样的草稿 这个实现需要给定的非类型模板参数int D,但我想要不带D的东西,比如重塑({1,2,3,4,5,6},{2,3})