我怀疑我隐约知道我观察的原因,但我想要确认或更正并对其进行一些解释。
我有以下代码:
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
的副本已被调用?
答案 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
,编译器就会知道它并且可以优化它。