为什么C ++不支持基于动态数组循环的范围?

时间:2017-11-18 07:19:09

标签: c++

为什么C ++不支持基于动态数组循环的范围?就是这样:

int* array = new int[len];
for[] (int i : array) {};

我刚发明for[]语句与new[]delete[]押韵。据我所知,运行时具有可用数组的大小(否则delete[]无法工作),因此理论上,基于for循环的范围也可以使用。它不起作用的原因是什么?

6 个答案:

答案 0 :(得分:12)

  

它不起作用的原因是什么?

基于范围的循环,如

 for(auto a : y) {
     // ...
 }

只是以下表达式的语法糖

 auto endit = std::end(y);
 for(auto it = std::begin(y); it != endit; ++it) {
     auto a = *it;
     // ...
 }

由于std::begin()std::end()不能与普通指针一起使用,因此不能使用分配有new[]的指针来应用。

  

据我所知,运行时具有可用数组的大小(否则delete[]无效)

delete[]如何跟踪用new[]分配的内存块(其大小不一定与用户指定的大小相同),这是完全不同的事情,编译器很可能甚至不知道这是如何实现的。

答案 1 :(得分:6)

当你有这个:

int* array = new int[len];

这里的问题是你的变量array根本不是一个数组。它是指针。这意味着它只包含一个对象的地址(在本例中是使用new创建的数组的第一个元素)。

对于基于工作的范围,编译器需要两个地址,即数组的开头和结尾。

所以问题是编译器没有足够的信息来执行此操作:

// array is only a pointer and does not have enough information
for(int i : array)
{
} 

答案 2 :(得分:3)

这与动态数组无关,它更为通用。当然,对于动态数组,存在一个可以调用析构函数的大小(但请记住,标准并没有说明任何内容,只是调用delete []按预期工作)。

问题在于指针通常是指针指示你不能告诉它是否对应于任何类型的......什么?

数组衰减到指针,但给出一个指针,你能说什么?

答案 3 :(得分:3)

int* array = new int[len];
for[] (int i : array) {}

必须解决几点问题;我会一次解决一个问题。

  

运行时是否知道数组的大小?

在某些条件下,它必须。正如您所指出的,对delete[]的调用将调用每个元素的析构函数(按预留顺序),因此必须知道有多少元素。

但是,通过不指定必须知道和访问元素的数量,C ++标准允许实现在不需要调用析构函数时省略它(std::is_trivially_destructible<T>::value求值为{{1} })。

  

运行时可以区分指针和数组吗?

一般来说,没有。

当你有一个指针时,它可以指向任何东西:

  • 单个项目或数组中的项目
  • 数组中的第一项或任何其他项
  • 堆栈上的数组,或堆上的数组,
  • 只是一个数组,或更大对象的数组部分。

这就是true存在的原因,在这里使用delete[]会有误。使用delete,用户说明:此指针指向堆分配的数组的第一项

然后,实现可以假设,例如,在第一个项之前的8个字节中,它可以找到数组的大小。如果不保证这一点,那8个字节可以是任何东西。

  

那么,为什么不一直走下去创建delete[]

有两个原因:

  1. 如前所述,今天的实施可以在许多要素上消除规模;使用这种新的for[] (int i : array)语法,在每种类型的基础上将不再可能。
  2. 不值得。
  3. 说实话,for[]new[]是旧时代的遗物。他们非常尴尬:

    • 必须事先知道元素的数量,并且不能更改,
    • 元素必须是默认可构造的,否则为C-ish,

    并且使用不安全:

    • 用户无法访问元素数量。

    在现代C ++中,通常没有理由使用delete[]new[]。大多数时候应该首选delete[];在容量多余的少数情况下,std::vector仍然更好(因为它跟踪大小)。

    因此,如果没有合理的理由继续使用这些语句,就没有动力去包含专门用于处理它们的新语义结构。

    任何人都应该有足够的动力来提出这样的建议:

    • 对当前优化的抑制,违反了C ++哲学&#34;你不会为你不使用的东西买单&#34;,很可能会对他们不利,
    • 包含新语法,当现代C ++提案尽可能地避免它时(为了定义库std::dynarray),也可能会对它们持有。< / LI>

    我建议您只使用std::variant

答案 4 :(得分:1)

array不是数组,而是指针,并且没有关于“数组”的大小的信息。因此,编译器无法推断出此数组的beginend

请参阅range based for循环的语法:

{
   auto && __range = range_expression ; 
   for (auto __begin = begin_expr, __end = end_expr; 
   __begin != __end; ++__begin) { 
   range_declaration = *__begin; 
   loop_statement 
   } 
} 
  

range_expression - 表示合适序列的任何表达式   (开头和结束成员函数的数组或对象)   或定义了自由函数,见下文)或braced-init-list。

auto在编译时工作。因此,begin_exprend_expr不会扣除运行时间。

答案 5 :(得分:1)

原因是,只给出指针array的值,编译器(和你的代码)没有关于它指向的信息。唯一知道的是array的值是单个int的地址。

它可以指向静态分配的数组的第一个元素。它可以指向动态分配的数组中间的元素。它可以指向数据结构的成员。它可以指向数据结构中的数组元素。名单还在继续。

您的代码将针对指针指向的内容进行假设。它可以假设它是一个由50个元素组成的数组。您的代码可以访问len的值,并假设array指向len元素数组的(第一个元素)。如果您的代码正确,则所有代码都按预期工作。如果您的代码出错(例如,访问具有5个元素的数组的第50个元素),那么行为就是未定义的。它是未定义的,因为可能性是无穷无尽的 - 用于跟踪任意指针实际指向的内容(超出该地址处有int的信息)将是巨大的。

您从array指向new int[len]结果的假设开始。但是该信息不存储在array本身的值中,因此编译器无法返回值len。这将是您的&#34;基于范围的&#34;工作方法。

虽然,是的,给定array = new int[len]delete [] array调用的机制将确定array具有len个元素,然后释放它们。但如果delete [] array来自array表达式以外的其他内容,则new []也会有未定义的行为。甚至

  int *array = new int;
  delete [] array;

给出未定义的行为。 &#34;运行时&#34;在这种情况下,array实际上是单个动态分配的int(而不是实际数组)的地址。所以不需要处理这个问题。