提问人:glades 提问时间:5/25/2022 最后编辑:Chrisglades 更新时间:6/19/2022 访问量:1861
为什么允许 std::initializer_list 不指定大小并同时分配堆栈?
How come std::initializer_list is allowed to not specify size AND be stack allocated at the same time?
问:
我从这里了解到不需要分配堆内存。这对我来说很奇怪,因为您可以在不指定大小的情况下接收对象,而对于数组,您始终需要指定大小。尽管初始值设定项在内部的列表几乎与数组相同(如帖子所示)。std::initializer_list
std::initializer_list
我很难理解的是,使用 C++ 作为静态类型语言,每个对象的内存布局(和大小)必须在编译时固定。因此,每个类型都是另一种类型,我们只是从一个通用模板中生成这些类型。但是对于 ,此规则显然不适用,因为内存布局(虽然它可以从传递给其构造函数的参数派生)不需要考虑接收函数或构造函数。只有当类型堆分配内存并且仅保留存储来管理该内存时,这对我来说才有意义。那么区别将很像 和 ,而对于后者,您也不需要指定大小。std::array
std::initializer_list
std::array
std::vector
然而,它不使用堆分配,正如我的测试所示:std::initializer_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";
}
这怎么可能?我可以为我自己的类型编写类似的实现吗?这真的可以让我免于在演奏编译时管弦乐队时吹掉我的二进制文件。
编辑:我应该注意,我想问的问题不是编译器如何知道它应该用什么大小实例化初始值设定项列表(可以通过模板参数推导来实现),而是它如何与初始值设定项列表的所有其他实例化不同(因此为什么您可以将不同大小的初始值设定项列表传递给同一函数)。
答:
问题是,它不会将物体固定在自身内部。实例化时,编译器会注入一些额外的代码以在堆栈上创建临时数组,并将指向该数组的指针存储在initializer_list中。就其价值而言,initializer_list只不过是一个具有两个指针(或一个指针和一个大小)的结构:std::initializer_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的寿命。
...而对于数组,您始终需要指定大小 ...
你的意思是喜欢
int a[] = {2, 3, 2, 6, 7};
?
我很难理解的是,使用 C++ 作为静态类型语言,每个语言的内存布局(和大小)都必须在编译时固定。
初始值设定项列表的大小在编译时与上面数组的大小一样固定 - 它是固定的,因为您在编译之前显式地编写了带支撑的表达式。{2, 3, 2, 6, 7}
这怎么可能?我可以为我自己的类型编写类似的实现吗?
你不能拦截一个 braced-init-list 的解析,不行。如您所见,处理列表初始化的规则非常具体。
但是,它旨在轻量级,因此您可以直接使用它。正如另一个答案所说,您可以将其视为一个普通的隐式大小数组,并隐式转换为类似范围的视图。std::initializer_list
只是添加一些我自己的随机思考,实际上是一种有趣的动物 - 它是一种嵌合体,部分是 STL,部分是编译器结构。std::initializer_list
如果查看相应的 STL 头文件,将找到定义 API 的定义。但实际上,实际实现是内置于编译器中的,所以当你写的时候,比如说:
std::initializer_list <int> l = { 1, 2, 3, 4, 5 };
编译器“啊哈!一个初始值设定项列表(以及一个要启动的 's 列表),我知道那是什么,我会构建一个”。事实就是如此。STL 本身没有代码可以做到这一点。int
换句话说,对于编译器来说,在某种程度上,它是一种本机类型。只是它不是,不完全是,因此它是独一无二的,是一个非常精选的俱乐部的成员(请参阅评论)。std::initializer_list
评论
std::type_info
std::source_location
int x = 42;
您似乎有一个误解,即您需要在编译时已知的大小才能在堆栈上分配。事实上,从技术上讲,没有技术理由禁止在运行时确定大小的对象从堆栈上。事实上,C 明确允许它使用可变长度数组。关于这个话题还有另一个问题,尽管它问的是 C。
虽然我不知道实际执行手动堆栈分配的合理C++方法(这是一个坏主意,而不是真正的C++),但没有什么能阻止编译器为您执行此操作。alloca()
堆栈分配非常简单 - 其他人可能会纠正我,但据我所知,它归结为简单地增加堆栈指针寄存器中的值。
评论
std::initializer_list
std::initializer_list
std::initializer_list
constexpr construt_at
int a[] = {1,2,3};
const char s[] = "Hello World";
s
const char *s = "Hello World";