【问题标题】:What constructor or operator is used in a return (C++)返回中使用什么构造函数或运算符(C++)
【发布时间】:2011-08-18 21:24:59
【问题描述】:

我运行这段代码来试验复制构造函数和赋值运算符

class AClass {

    private:
        int a;

    public:
        AClass (int a_) : a(a_) {  
            cout << " constructor AClass(int) " << a << endl;
        }

        AClass(const AClass & x) : a(x.a) { 
            cout << " copy constructor AClass(const AClass &) " << a << endl;
        }

        AClass & operator=(const AClass & x) { 
                a = x.a;
                cout << " AClass& operator=(const AClass &) " << a - endl;
                return *this;
        }
};

AClass g () {
    AClass x(8);
    return x;
}

int main () {

    cout << " before AClass b = g() " << endl;
    AClass b = g();
    cout << " after" << endl;

    cout << " before AClass c(g()) " << endl;
    AClass c  (g());
    cout << " after" << endl;
}

发现return x;没有出现消息 为什么? 不应该调用复制构造函数或 operator= 吗?

这是输出:

在 AClass b = g() 之前 构造函数 AClass(int) 8 后 在 A 类 c(g()) 之前 构造函数 AClass(int) 8 后

【问题讨论】:

  • 不要在您的问题中使用&lt;pre&gt;&lt;code&gt;,请选择代码示例并在编辑器中按{} 按钮。它在保留原始代码方面做得更好。
  • 由于您正在试验,如果您在g() 中将x 设为非自动变量,那么知道您可以强制复制可能会有所帮助,例如:AClass *x = new AClass(8); return (*x);。当然,编写这样的代码会让你晋升为 S.S. MemoryLeak 的队长。

标签: c++ return copy-constructor assignment-operator


【解决方案1】:

在 C++ 中,几乎所有情况下都允许编译器删除对复制构造函数的调用,即使复制构造函数有副作用,例如打印出消息。作为推论,它还允许在几乎任何需要的位置插入对复制构造函数的调用。这使得编写程序来测试您对复制和赋值的理解有点困难,但这意味着编译器可以积极地删除现实代码中不必要的复制。

【讨论】:

    【解决方案2】:

    如果你想看看编译器会调用什么构造函数,你必须打败RVO。替换您的 g() 函数:

    int i;
    AClass g () {
        if(i) {
          AClass x(8);
          return x;
        } else {
          AClass x(9);
          return x;
        }
    }
    

    【讨论】:

    • 我也不确定这是否可行。编译器不需要在单独的内存块中构造x 的两个可能版本。如果你有两个不同的变量,在同一个范围内,并且你有条件地返回它们中的任何一个,你可能会有更好的运气。
    • 实际上,在没有优化的情况下,它在 GCC 中确实产生了明显不同的结果。
    【解决方案3】:

    这称为复制省略。编译器几乎可以在任何情况下省略副本。最常见的情况是 RVO 和 NRVO,这基本上会导致就地构造返回值。我将演示转换。

    void g (char* memory) {
        new (memory) AClass(8);
    }
    
    int main () {
    
        char __hidden__variable[sizeof(AClass)];
        g(__hidden__variable);
        AClass& b = *(AClass*)&__hidden__variable[0];
        cout -- " after" -- endl;
    
        // The same process occurs for c.
    }
    

    代码效果相同,但现在只存在一个AClass实例。

    【讨论】:

      【解决方案4】:

      这称为“返回值优化”。如果对象是按值返回的,则允许编译器在函数返回后在调用者可用的位置构造它;在这种情况下,不会调用复制构造函数。

      也允许把它当作一个普通的自动变量,并在返回时复制它,所以复制构造函数必须可用。是否调用它取决于编译器和优化设置,因此您不应依赖任何一种行为。

      【讨论】:

        【解决方案5】:

        编译器可能已经优化了复制构造函数调用。基本上,它移动对象。

        【讨论】:

        • 鉴于 C++0x 中所做的更改,我会犹豫是否要简化。
        • C++0x 使得明确移动物体成为可能。即使那样,编译器也可以完全忽略你,而不是调用你的移动构造函数(这让我很困惑)。
        【解决方案6】:

        允许编译器在这种情况下省略复制。这称为Return Value Optimization

        【讨论】:

        • 谢谢,但是如果 RVO 没有完成,会执行什么:复制构造函数还是 operator=?我已经创建了 private operator= 并且两个调用仍然可以编译,所以结论是将使用复制构造函数。正确的?再次感谢
        • @ciber:一般来说,是的,复制构造函数用于将参数传入和传出函数。我不相信允许以这种方式省略赋值运算符,但不要引用我的话。
        • @Dennis 不,不是。赋值操作永远不会被编译器调用,只会被程序员显式调用。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-21
        • 1970-01-01
        • 1970-01-01
        • 2011-02-20
        • 2011-05-21
        相关资源
        最近更新 更多