我在理解在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 ++中完全掌握返回值吗? ;)从函数返回对象的推荐方法是什么(如果有的话) - 任何经验法则?
答案 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)是的。