自从我上次使用C ++做一些事情以来已经很多年了。这些天,有人问我在C ++的学校项目上提供一些帮助,我对我所见过的语言的“功能”很感兴趣,这种功能很好,但我预计它不起作用。
我记得,我可以在堆上或堆栈上创建类的实例:
int main() {
MyClass *inHeap = new MyClass();
MyClass inStack = MyClass();
}
据我所知,第一个变量,inHeap
将使编译器在main
堆栈帧中保留一些堆栈足以容纳指针(4 bytes?8 bytes?类似的东西),它指向生活在堆中的对象的实际实例。
此外,第二个变量,inStack
将使编译器保留堆栈足以在MyClass
堆栈中保存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
实例保留了内存。这存在吗?这是某种优化以避免制作可能昂贵的副本吗?
作为最后一个问题,当我在堆栈中创建一个实例时,究竟是什么时候调用它的析构函数?创建它的范围何时结束?
答案 0 :(得分:8)
如果您正在做这样的事情:
MyClass createInStack()
{
MyClass return_value;
return return_value;
}
int main()
{
MyClass inStack = createInStack();
}
然后是,c
在createInStack()
的堆栈上进行逻辑创建,然后逻辑上返回它的副本,然后将其复制到inStack
中的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
而只是不创建它,但你可以非常肯定,至少命名的返回值优化将避免大量复制。
答案 1 :(得分:1)
还应该指出,调用new
并不是一种无成本的操作。它可能会产生很大的开销,并且它肯定比在堆栈上制作本地副本使用更多内存 - 当然,delete
必须由程序员处理和记住,并且还需要超过零时间。
通常,这是一个更好的设计决策,并了解正在发生的事情(以及理解前面答案中已经涵盖的“返回值优化”)。一个非常大的类需要很长时间才能复制,但是只有一些值的小类,在堆栈上复制可能比分配和释放它更快。