C++返回本地对象

27

我和几个同事正在就当一个局部变量(在栈上分配)从C++方法返回时会发生什么进行辩论。

以下代码可以通过单元测试,但我认为这只是因为该单元测试幸运地没有尝试重用obj使用的栈上内存。

这是否可行?

static MyObject createMyObject() {
    MyObject obj;
    return obj;
}

它能工作,但与直接在目标处声明MyObject(因为你只是返回一个_副本_)没有什么不同--即MyObject x = createMyObject();与更高效的MyObject x;达到了相同的效果。 - Kerrek SB
1
@Crashworks:这并没有帮助区分定义行为和运气之间的区别... - Oliver Charlesworth
另一方面,“MyObject&createMyObject”在这种模式下不起作用,除非您像其中一个答案中所述使用静态对象。如果返回“new MyObject”,则“MyObject * createMyObject”将起作用。 - Neil
2
如果上述结构不安全,你认为返回值有什么作用? - Dennis Zickefoose
你只需要一个带有错误复制构造函数的对象就能赢得你的争论。一个数组将证明你的观点。 - Hans Passant
显示剩余3条评论
8个回答

39
拷贝构造函数被调用以复制本地对象,调用者接收到的就是这个复制品。
编译器可能会在拷贝省略过程中消除这个复制,但这取决于编译器——你无法控制它。
如果你返回一个指针引用指向本地对象,这种模式有可能会产生你担心的问题。

5
“但仅当可观察到的效果相同时。” - 不,复制省略的整个重点在于,即使复制构造函数具有可观察的副作用,也可以消除复制,从而使结果不同。实现不需要特殊规则来执行不改变可观察效果的代码转换,这可以在as-if规则下完成。 - Steve Jessop
好的 - 现在这个更有意义了。听起来这段代码是安全的,会正常工作,我不必担心段错误。 - Brian
嗨,如果我通过MyClass *obj = new MyClass();在函数内部创建一个本地对象,返回对象指针,并且该方法的签名是MyClass * myMethod(){...},如果我像这样调用它MyClass *retObj = myMethod();,然后解引用它像这样retObject->someMethod();,它总是有效的。这是应该的吗,还是我运气好? - SexyBeast
@Cupidvogel 这是与问题完全不同的情况。由于您使用了 new 来创建并返回指针,因此对象不会被复制,而是会继续存在,直到您 delete 指针。当您返回指向 本地 对象的指针时,而不是使用 new 创建的对象时,就会遇到问题。 - Mark Ransom
我明白了。这是因为使用 new 在堆上创建对象,该对象会继续存在,无论调用和退出哪个函数,而不使用它则在栈上创建对象,该对象仅限于函数本地,并且不能保证在函数退出后仍然存在。 - SexyBeast
谢谢。那非常有帮助。我不得不再次检查您的显示图片,以确保它不是史蒂夫·乔布斯。干杯! - SexyBeast

6

创建了obj,然后使用对象的复制构造函数从方法/函数中复制出它。

您可以通过将其声明为static来使此obj不在堆栈中。返回对象也会返回一个副本,但对象不会在每次调用函数时被创建。然后,您可以将该对象作为引用返回:

static MyObject & createMyObject() {
    static MyObject obj;
    return obj;
}

这里不允许复制,同时obj只被创建一次,在运行时其地址保持不变。


1
虽然在许多情况下需要小心处理静态对象,例如在多线程代码中或析构函数位于不同的共享库中。 - Neil

1

函数“返回本地对象”是可以的,因为编译器会将函数转换为实际上不返回值。相反,它将接受一个引用MyObject& __result,并使用将分配返回值即obj的本地对象来复制构造__result。在您的情况下,函数将被重写为:

static void createMyObject(MyObject& __result) {
    MyObject obj;

    // .. process obj
    // compiler generated invocation of copy constructor
    __result.MyObject::Myobject( obj );

    return;
}

每次调用createMyObject都会被转换为将引用绑定到现有对象的形式。例如,下面这种调用:

MyObject a = createMyObject();

将被转换为:

MyObject a;  // no default constructor called here
createMyObject(a);

但是,如果你返回一个指向局部对象的引用或指针,编译器无法完成转换。你将会返回一个指向已经销毁的对象的引用或指针。


1
你通过值返回对象,因此将调用它的复制构造函数,并将原始对象的副本返回并存储在调用者的堆栈中。如果此方法返回局部变量的指针(或引用),则会失败。

1
返回 MyObject 的副本。如果 MyObject 有一个正确复制所有内容的复制构造函数,那么这应该是可以的。请注意,即使没有显式列出,它也可能具有复制构造函数 - 编译器定义的默认复制构造函数(逐成员赋值)可能适用于您的目的。

1
在这个例子中,MyObject 是通过值返回的。这意味着它的一个副本被创建并传递回调用函数。(在某些情况下,编译器可以优化掉多余的复制,但只有当这等价于调用 MyObject 的复制构造函数并将复制放入堆栈时才能进行优化。)

1
假设其他人只是忽略了这个问题中显而易见的困惑来源 -- static:
你没有声明在 createMyObject 中创建并返回的 MyObject 实例具有静态存储期;相反,你声明了函数 createMyObject 具有内部链接。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接