2013-09-28 118 views
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 

我的理解是,在foo对象是在栈上创建,然后在销毁返回语句,因为它是范围的结尾。返回是通过对象复制(复制构造函数)完成的,稍后将其分配给主(赋值运算符)中的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++中的返回值? ;)从函数返回一个对象的建议方法是什么(如果有的话) - 对此的任何经验规则?

+1

'MyClass c1 = foo();'不是一个赋值。这是一个*复制初始化*。 – dyp

+0

嗯,我的错。所以在所有这些情况下'operator ='不会被调用? – jesper

+2

确实。经验法则:除非你想暴露内部(像'std :: vector :: operator []'返回一个引用),否则不要返回拥有的原始指针(即一个必须被删除的),不要返回一个const对象(因为它禁止了移动语义)。 – dyp

回答

2

案例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)。

0

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

3

我可以补充一点:case#2是C++语言中未定义行为的一种情况,因为返回对本地变量的引用是非法的。这是因为局部变量具有精确定义的生命周期,并且 - 通过返回引用 - 您将返回对函数返回时不再存在的变量的引用。因此,您展现未定义的行为,并且给定变量的值实际上是随机的。 As is the result of the rest of your program, since Anything at all can happen。当您尝试做这样的事情

大多数编译器会发出警告(通过引用,或通过地址返回一个局部变量) - 海湾合作委员会,例如,告诉我这样的事情:

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

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