返回值(引用、指针和对象)

Returning value (reference, pointer and object)

本文关键字:对象 指针 引用 返回值      更新时间:2023-10-16

我很难理解在C++中返回值背后到底做了什么。

让我们有以下代码:

class MyClass {
public:
    int id;
    MyClass(int id) {
        this->id = id;
        cout << "[" << id << "] MyClass::ctorn";
    }
    MyClass(const MyClass& other) {
        cout << "[" << id << "] MyClass::ctor&n";
    }
    ~MyClass() {
        cout << "[" << id << "] MyClass::dtorn";
    }
    MyClass& operator=(const MyClass& r) {
        cout << "[" << id << "] MyClass::operator=n";
        return *this;
    }
};
MyClass foo() {
    MyClass c(111);  
    return c;        
}
MyClass& bar() {
    MyClass c(222);
    return c;
}
MyClass* baz() {
    MyClass* c = new MyClass(333);
    return c;
}

我使用 gcc 4.7.3。

案例1

当我打电话时:

MyClass c1 = foo();
cout << c1.id << endl;

输出为:

[111] MyClass::ctor
111
[111] MyClass::dtor

我的理解是,在堆栈foo对象是在堆栈上创建的,然后在返回语句时销毁,因为它是范围的结束。返回是通过对象复制(复制构造函数(完成的,稍后在 main(赋值运算符(中分配给c1。如果我是对的,为什么没有复制构造函数或赋值运算符的输出?这是因为RVO吗?

案例2

当我打电话时:

MyClass c2 = bar();
cout << c2.id << endl;

输出为:

[222] MyClass::ctor
[222] MyClass::dtor
[4197488] MyClass::ctor&
4197488
[4197488] MyClass::dtor

这是怎么回事?我创建变量然后返回它,变量被销毁,因为它是范围的结束。编译器正在尝试通过复制构造函数复制该变量,但它已经被销毁了,这就是为什么我有随机值?那么,c2到底是什么呢?

案例3

当我打电话时:

MyClass* c3 = baz();
cout << c3->id << endl;

输出为:

[333] MyClass::ctor
333

这是最简单的情况吗?我返回一个动态创建的指针,它位于堆上,因此 memmory 是分配的,而不是自动释放的。当未调用析构函数并且我有内存泄漏时,就是这种情况。我说的对吗?

是否有任何其他情况或事情不明显,我应该知道完全掌握C++中的返回值? ;)从函数(如果有的话(返回对象的推荐方法 - 任何经验法则?

我可以补充一点,案例 #2 是C++语言中未定义行为的情况之一,因为返回对局部变量的引用是非法的。这是因为局部变量具有精确定义的生存期,并且 - 通过引用返回它 - 您将返回对函数返回时不再存在的变量的引用。因此,您表现出未定义的行为,并且给定变量的值实际上是随机的。正如程序其余部分的结果一样,因为任何事情都可能发生

当您尝试执行以下操作时,大多数编译器都会发出警告(通过引用或地址返回局部变量( - 例如,gcc 告诉我这样的事情:

bla.cpp:37:13: warning: reference to local variable ‘c’ returned [-Wreturn-local-addr]

但是,您应该记住,当发生可能表现出未定义行为的语句时,编译器根本不需要发出任何类型的警告。但是,必须不惜一切代价避免这种情况,因为它们实际上永远不会正确。

案例 1

MyClass foo() {
    MyClass c(111);  
    return c;        
}
...
MyClass c1 = foo();

是可以应用 RVO 的典型情况。这称为复制初始化,并且不使用赋值运算符,因为对象是就地创建的,这与以下情况不同:

MyClass c1;
c1 = foo();

在构造c1的地方,构造foo()中的临时c,构造[c的副本],cc的副本分配给c1,[c的副本被销毁]并销毁c。(具体发生的情况取决于编译器是否消除了正在创建的c的冗余副本(。

案例2

MyClass& bar() {
    MyClass c(222);
    return c;
}
...
MyClass c2 = bar();

调用未定义的行为,因为您要返回对局部(临时(变量的引用c ~ 具有自动存储持续时间的对象。

案例3

MyClass* baz() {
    MyClass* c = new MyClass(333);
    return c;
}
...
MyClass c2 = bar();

是最直接的,因为您可以控制发生的事情,但会产生非常不愉快的后果:您负责内存管理,这就是为什么您应该始终避免在可能的情况下进行此类动态分配的原因(并且更喜欢情况 1(。

1( 是的。
2(你有一个随机值,因为你的复制c'tor和operator=不复制id的值。但是,假设删除对象后不依赖于对象的值,这是正确的。
3(是的。