3

自从我上次用 C++ 做某事以来已经有很多很多年了。这些天来,有人向我寻求 C++ 学校项目的帮助,我对我所见过的语言的一个“特性”很感兴趣,它运行良好,但我预计它不起作用。

我记得,我可以在堆上或堆栈上创建类的实例:

int main() {
  MyClass *inHeap = new MyClass();
  MyClass inStack = MyClass();
}

据我所知,第一个变量,inHeap将使编译器在main堆栈帧中保留一些堆栈,足以容纳一个指针(4 个字节?8 个字节?类似的东西),它指向对象的实际实例住在堆里。

此外,第二个变量,inStack将使编译器保留足够的堆栈以在堆栈帧中保存MyClassright的完整实例。main

现在,关于我的问题。假设我有一个函数应该返回一个MyClass. 起初,我以为它只能返回堆中的实例:

MyClass *createInHeap() {
  return new MyClass();
}
int main() {
  MyClass* inHeap = createInHeap();
}

但我看到的是以下内容:

MyClass createInStack() {
  MyClass c = MyClass();
  return c;
}
int main() {
  MyClass inStack = createInStack();
}

这里到底发生了什么?

  • MyClass的堆栈帧中是否保留了实例的内存createInStack?如果是这种情况,这段代码是否会强制将实例复制main到函数createInStack返回时的堆栈帧中?这个复制是如何执行的,即它只是在函数中自动为我调用复制构造main函数吗?

  • 我想到的另一种可能性是编译器足够聪明,已经为堆栈帧MyClass中的实例保留了内存。main这存在吗?这是某种优化以避免制作可能昂贵的副本吗?

最后一个问题,当我在堆栈中创建了一个实例时,它的析构函数究竟是什么时候调用的?创建它的范围何时完成?

4

2 回答 2

9

如果你正在做这样的事情:

MyClass createInStack() 
{
  MyClass return_value;
  return return_value;
}

int main() 
{
  MyClass inStack = createInStack();
}

然后是的,c在堆栈中逻辑上创建,createInStack()然后逻辑上返回它的副本,然后复制到inStackin 中main

由于所有这些逻辑复制,有一个常见的误解是按值返回效率低下。但是,由于命名返回值优化,这并不是实际发生的情况。调用函数中的构造步骤只是推迟到被调用函数。它会是这样的(伪代码):

void createInStack(MyClass &return_value)
{
  return_value.MyClass(); // construct return_value (not actually valid syntax)
}

int main()
{
  MyClass inStack; // except don't call the constructor
  createInStack(inStack);
}

如您所见,没有实际的复制发生。

此外,编译器可能会进行其他优化。它甚至可能决定inStack从不使用它,只是不创建它,但您可以非常确定,至少命名返回值优化将避免大量复制。

于 2013-04-28T17:41:03.667 回答
1

还应该指出,调用new不是一种无成本的操作。它可能有很大的开销,而且它肯定比在堆栈上制作本地副本使用更多的内存——当然,delete程序员必须处理和记住它,而且花费的时间也超过零。

因此,像往常一样,这是一个更好的设计决策,并了解正在发生的事情(以及了解先前答案中已经涵盖的“返回值优化”)。一个非常大的类需要很长时间来复制,但是一个只有几个值的小类,在堆栈上复制可能比分配和释放它更快。

于 2013-04-28T18:21:57.713 回答