为什么不总是实例化堆栈上的对象?C++

Why not always instantiate objects on the stack? C++

本文关键字:对象 C++ 堆栈 实例化 为什么不      更新时间:2023-10-16

剧透只包含背景和上下文

!这篇文章提到,为了解决当对象超出范围时它被解除分配的事实,只需将堆栈分配的对象返回对象,使其保持在范围内。这显然会在其他地方的堆栈上复制对象。 这家伙甚至证实,您应该始终更喜欢分配堆叠。 在C++中,使用如下内容:

Object* my_object = new Object();

!将对象动态实例化到堆中,但 ! Object my_object = Object();

!实例化堆栈上的对象。堆栈的大小是有限的,而堆实际上没有(物理限制除外)。而且,根据这篇文章,堆栈访问时间要快得多,当然,当它超出范围时,释放是自动的。


正在尝试创建一个速度绝对至关重要的应用程序,我不能只在 main 中的堆栈上实例化我的所有对象,然后简单地将嵌套范围内的每个实例保存到外部容器中吗?

我自己使用包含属性"id"的简单 Node 类对此进行了测试。我实例化了堆栈上的节点,将它们放在一个向量中,这样它们就不会被解除分配,然后(只是为了检查)我将新项目分配给堆栈,然后检查以确保先前分配的数据仍然存在。我可以继续在有点大规模的问题上实现它吗?

int main()
{
  vector<Node> stack_nodes;
  for (int i = 0; i < 2; ++i)
  {
    stack_nodes.push_back(Node(i)); // push newly copied stack-allocated objects so they don't die 
  }
  Node new_node1 = Node(3); // allocate two more to test stack memory overwriting 
  Node new_node2 = Node(4);
  cout << stack_nodes.at(1).getID(); // outputs 1! It's still there?
  return 0;
}

编辑:请参阅下面的评论。当您从创建堆栈分配的对象的作用域返回堆栈分配对象时,将创建该对象的副本。该副本也在堆栈上吗?如果我将该复制的对象分配给在 main 范围内声明的向量,该对象是否仍在堆栈上?

在某些情况下,您当然可以为某些程序执行此操作。例如,回到时间深处,Fortran被定义,以便程序使用的所有数据都可以静态分配(而且非常常规)。

同时,它非常有限和有问题。仅举几个例子,它排除了(几乎)所有递归,这对于处理某些类型的递归数据结构(例如树)非常方便。

这也意味着你的所有变量本质上都变成了全局变量,因此(例如)程序中的任何代码都可以读取和/或写入程序中的几乎任何变量。使用这种模型的Fortran和(早期版本)BASIC等语言的经验表明,开发目前被视为中小型程序的程序需要大量的纪律,而开发现在通常被视为大型系统的方法可能几乎是不可能的。代码不同部分之间的依赖关系变得如此之快,以至于几乎不可能确定在哪里使用什么,哪些部分依赖于其他部分等。

我怀疑这在实践中是合理的。分配堆栈空间的开销开始时非常小,以至于消除它根本不会显着提高速度。事实上,它可能很容易做到恰恰相反。预先分配变量意味着每个变量(几乎是必要的)都存在于内存的独特部分中。其中相对较大的百分比将指向给定时间当前不在缓存中的内存部分,因此您最终会得到较差的引用位置,从而导致缓存使用率不佳。

在输入函数时分配本地数据意味着大多数变量位于堆栈顶部或接近堆栈顶部。由于您几乎一直在使用堆栈顶部附近的内存,因此该内存大部分时间都保留在缓存中,因此几乎所有的内存访问都会访问缓存。

分配所需的时间通常(远)低于 1%,但访问主内存而不是缓存的损失通常至少为 10 倍,而且通常要高得多(20-50 倍很常见)。您的里程会因数据访问模式而异,但您有很大的损失潜力,而且(充其量)只有很小的几率获得微小的收益。

总结:这是一个糟糕的想法。它更有可能造成很多伤害,甚至比一点点好处。

我不能在 main 中的堆栈上实例化我的所有对象吗?

如果你能解释你需要的所有对象,是的

哎呀,COBOL认为这种方法是给定的。 "这是你需要的每一个变量......"

我可以继续在有点大规模的问题上实现它吗?

拥有无限的记忆,是的。 总是。

对于有限内存,您可能希望管理对象生存期,并且仅要求随时实际需要的内存。