按值传递和复制省略优化

9
我找到了这篇关于C++技术的文章:https://web.archive.org/web/20120707045924/cpp-next.com/archive/2009/08/want-speed-pass-by-value/ 作者给出了建议:
不要复制函数参数,相反,通过值传递它们,让编译器完成拷贝。
然而,在文章中提供的两个例子中,我并没有完全理解会有什么好处。
// Don't
T& T::operator=(T const& x) // x is a reference to the source
{ 
    T tmp(x);          // copy construction of tmp does the hard work
    swap(*this, tmp);  // trade our resources for tmp's
    return *this;      // our (old) resources get destroyed with tmp 
}

vs

// DO
T& operator=(T x)    // x is a copy of the source; hard work already done
{
    swap(*this, x);  // trade our resources for x's
    return *this;    // our (old) resources get destroyed with x
}

在这两种情况下,都会创建一个额外的变量,那么好处在哪里呢? 我唯一能看到的好处是,在第二个示例中将temp对象传入。

1
如果源是临时的:obj = T();obj = foo();,其中 foo() 返回一个 T - juanchopanza
1
那其实不是很好的建议。它在接口中暴露了本应该是实现细节的内容(无论你是否复制参数),这是极其糟糕的软件工程。也许有时候分析器会告诉你必须这么做,但除此之外,你应该遵循编码指南。(普遍的指南似乎是通过引用传递类类型,其他一切都通过值传递,尽管这也可以被视为过早优化。) - James Kanze
4
“this function copies the argument's state” 是一个合理的接口特征。除其他方面外,它还会对成本产生影响,告诉您必须实现参数类型的哪些特性,甚至向用户提供功能信息。C++11 的天才之处之一在于复制和移动是重要且独立的数据操作,并将其公开是很重要的。 - Yakk - Adam Nevraumont
1个回答

8

关键在于根据操作者的调用方式,可能会省略复制。假设您像这样使用操作者:

extern T f();
...
T value;
value = f();

如果参数采用 T const&, 编译器只能保留临时变量并传递一个引用给你的赋值运算符。另一方面,如果你通过值传递参数,即使用 T,则从 f() 返回的值可以位于此参数所在的位置,从而省略一个副本。当然,如果赋值的参数是某种形式的左值,则始终需要进行复制。


4
就异常而言,T const& 的情况下拷贝构造函数可以在被调用的函数内部抛出异常,但是对于 T 而言,异常将会在调用者的上下文中抛出(这意味着即使拷贝构造函数可能会抛出异常,赋值运算符也可以标记为noexcept)。 - Simple
1
第二个案例中的复制还可以被替换为移动,这与省略不同。 - Yakk - Adam Nevraumont
@DietmarKühl 感谢您的回复,不幸的是,您的答案对我来说毫无意义。 所以,让我来解释一下您的回答,并尝试看看它是否有意义(对您和其他读者)。到目前为止,我得到了这个:我们有两个“operator(s)= first”,一个是常规类赋值运算符“T::operator=(...)”,另一个是常规(函数)“operator=(...)”。 根据您的代码,最终赋值有两种可能性。 - newprint
第一种可能性是 value::operator=(f()),其中 rvalue 临时参数绑定到 const lvalue 引用参数(因为它是 const,所以可以这样做),并且因为参数 x 在 (*如文章中所讨论的) 内部是 lvalue,所以无论如何都会被复制到 tmp 中,即我们无法省略复制。从长远来看:

{temp_anon} 被创建 - 一个副本, tmp - 第二个副本

占用了 2 个对象的内存 + 2 个构造函数调用 + 2 个析构函数调用
- newprint
@DietmarKühl 第二种可能性是调用operator=(T x)。例如:operator=(f()); 在这种情况下,编译器已经为我们创建了x,我们不需要像之前那样复制它。从角度来看:

{temp_anon}被创建 - 一个副本

单个对象的内存被占用 + 1个构造函数调用 + 1个析构函数调用。我说得对吗?
- newprint
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接