2

我很难理解在 C++ 中返回值背后的真正作用。

让我们有以下代码:

class MyClass {

public:

    int id;

    MyClass(int id) {
        this->id = id;
        cout << "[" << id << "] MyClass::ctor\n";
    }

    MyClass(const MyClass& other) {
        cout << "[" << id << "] MyClass::ctor&\n";
    }

    ~MyClass() {
        cout << "[" << id << "] MyClass::dtor\n";
    }

    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

我的理解是 in fooobject 在堆栈上创建,然后在 return 语句时销毁,因为它是范围的结尾。返回是通过对象复制(复制构造函数)完成的,该对象复制稍后c1在 main(赋值运算符)中分配。如果我是对的,为什么复制构造函数和赋值运算符都没有输出?这是因为 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

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

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

4

3 回答 3

3

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

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

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

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

于 2013-09-28T21:48:03.450 回答
2

案例一

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

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

MyClass c1;
c1 = foo();

wherec1被构造,临时cinfoo()被构造,[副本c被构造],c或副本c被分配给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)。

于 2013-09-28T22:10:16.550 回答
0

1) 是的。
2)您有一个随机值,因为您复制 c'tor 并且operator=不复制id. 但是,您假设在删除对象后不依赖对象的值是正确的。
3) 是的。

于 2013-09-28T21:38:08.220 回答