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

时间:2013-09-28 21:34:16

标签: c++ optimization copy return return-value-optimization

我在理解在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

我的理解是在foo对象是在堆栈上创建的,然后在return语句中销毁,因为它是作用域的结尾。通过对象复制(复制构造函数)完成返回,稍后将其分配给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 ++中完全掌握返回值吗? ;)从函数返回对象的推荐方法是什么(如果有的话) - 任何经验法则?

3 个答案:

答案 0 :(得分:3)

我可以补充一点,#2是C ++语言中未定义行为的情况之一,因为返回对局部变量的引用是非法的。这是因为局部变量具有精确定义的生命周期,并且 - 通过引用返回它 - 您将返回对函数返回时不再存在的变量的引用。因此,您表现出未定义的行为,并且给定变量的值几乎是随机的。 As is the result of the rest of your program, since Anything at all can happen

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

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

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

答案 1 :(得分:2)

案例1

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

是可以应用RVO的典型情况。这称为复制初始化,因为对象是在适当的位置创建的,所以不使用赋值运算符,这与情况不同:

MyClass c1;
c1 = foo();

构建c1时,构建c中的临时foo(),[构建c的副本],c或{{1}的副本已分配给c,[{1}}的副本已被破坏],c1被破坏。 (究竟发生了什么取决于编译器是否删除了正在创建的c的冗余副本)。

案例2

c

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

案例3

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

是最简单的一个,因为你控制发生了什么然后带来非常不愉快的后果: 你负责内存管理 ,这是你应该尽可能避免动态分配这种情况的原因(并且更喜欢案例1)。

答案 2 :(得分:0)

1)是的。
2)您有一个随机值,因为您的副本c'tor和operator=不会复制id的值。但是,假设在删除对象后没有依赖对象的值,那么你是正确的 3)是的。

相关问题