返回右值?或者:为什么在`return <expression>`上调用复制构造函数?

时间:2016-09-21 03:32:45

标签: c++ return copy-constructor rvalue

我怀疑我隐约知道我观察的原因,但我想要确认或更正并对其进行一些解释。

我有以下代码:

template <class T>
class C
{

public:

    C () = default;

    C(const C& rhs) : mem(rhs.mem)
    {
        std::cerr << "copy" << "\n";
    }

    // does not call copy constructor twice
    // friend C operator<<(C& src, unsigned shift)
    // {
    //     std::cerr << 1 << "\n";
    //     C tmp(src);
    //     std::cerr << 2 << "\n";
    //     tmp <<= shift;
    //     std::cerr << 5 << "\n";
    //     return tmp;
    // }

    // does call copy constructor twice
    friend C operator<<(C& src, unsigned shift)
    {
        std::cerr << 1 << "\n";
        C tmp(src);
        std::cerr << 2 << "\n";
        return (tmp <<= shift);
    }

    friend C& operator<<=(C& src, unsigned shift)
    {
        std::cerr << 3 << "\n";
        src.mem <<= shift;
        std::cerr << 4 << "\n";
        return src;
    }

    T mem;
};

int main()
{
    C<int> c1;

    c1 << 3;
}

我有两个版本的C<T>::operator<<(C<T>, unsigned)

区别在于返回表达式的结果:

return (tmp <<= shift);

另一个返回一个变量:

return tmp

到目前为止,我认为这两个函数在语义上是相同的,而return (tmp <<= shift);的函数只是更好的样式,如return a + 1样式比int ret = a + 1; return ret更好。这显然不是这样,并且可能仅适用于原子数据类型。 return (tmp <<= shift);版本的输出如下所示:

1
copy
2
3
4
copy

另一个的输出如下:

1
copy
2
3
4
5

我的假设是正确的,因为return (tmp <<= shift);在调用tmp之后没有返回<<=,而是在{{1}之后创建一个新对象作为tmp的副本已被调用?

2 个答案:

答案 0 :(得分:1)

按值返回时,必须初始化返回值。返回值是此处C类型的对象。

return (tmp <<= shift);的情况下,这意味着(tmp << shift)C的初始值设定项。由于这是C类型的左值,这是一个复制结构。

对于return identifier;形式的return语句,有一个特殊规则,即使identifier是左值,它也可以是move-construction。但是这个规则并没有延伸到其他表达式(还)。

您的代码的其他版本确实激活了特殊规则:

tmp <<= shift;
return tmp;

此处tmp可被视为右值,使其可移动(这也使其成为复制省略)。

在您的测试中,您的编译器实现了copy-elision。要在没有copy-elision的情况下进行测试(如果您的编译器支持让用户配置它),您还需要为C提供一个移动构造函数。用户提供的复制构造函数禁止隐式生成移动构造函数。

答案 1 :(得分:0)

operator <<=可能不会返回对输入参数的引用(可能会返回对另一个static / global变量的引用),因此编译器不能轻易决定使用< strong>返回值优化,需要调用复制构造函数。

在您的问题中:当您编写return (tmp <<= shift)时,编译器不知道(tmp <<= shift)是否会返回对tmp的引用,但如果您编写return tmp,编译器就会知道它并且可以优化它。