【问题标题】:Returning value (reference, pointer and object)返回值(引用、指针和对象)
【发布时间】:2013-09-28 21:34:15
【问题描述】:

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

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

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

【问题讨论】:

  • MyClass c1 = foo(); 不是分配。这是一个复制初始化
  • 嗯,我的错。那么在所有这些情况下,operator= 都不会被调用?
  • 确实如此。经验法则:按值返回(并依赖于复制省略或移动语义),除非您想公开内部结构(例如 std::vector::operator[] 返回引用),不要返回拥有的原始指针(即必须删除的指针) , 不要返回 const 对象(因为它禁止移动语义)。

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


【解决方案1】:

我可以补充一下,案例 #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]

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

【讨论】:

    【解决方案2】:

    案例 1

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

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 2016-05-01
        • 2016-07-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多