C++ C++;关于收益值优化问题
这个网站上有很多关于返回值优化的问题(我想这是一个相当混乱的话题),但我似乎找不到一个能回答我特定问题的问题。如果我有这样的代码:C++ C++;关于收益值优化问题,c++,C++,这个网站上有很多关于返回值优化的问题(我想这是一个相当混乱的话题),但我似乎找不到一个能回答我特定问题的问题。如果我有这样的代码: returnType function() { stuff....} void someOtherFunction() { returnType var = function(); more stuff... } 我被告知编译器可能会决定在someOtherFunction()中不使用两个returnType实例。从逻辑上讲,我希望functio
returnType function() { stuff....}
void someOtherFunction()
{
returnType var = function();
more stuff...
}
我被告知编译器可能会决定在someOtherFunction()
中不使用两个returnType
实例。从逻辑上讲,我希望function()
将生成类型为returnType
的对象,someOtherFunction()
将通过复制构造函数(重载或非重载)将该值复制到临时值中。然后,我希望通过赋值将临时值复制到var
中(赋值可以重载,理论上可以具有任何类型的功能!),而var先前是通过默认构造函数初始化的
我看到了一个潜在的问题。如果someOtherFunction()
中没有此returnType
的临时副本,会发生什么情况?var
是否必须通过复制构造函数直接用function()
返回的值填充?如果是这样的话,难道不会永远调用赋值运算符吗?如果是这样的话,如果赋值运算符过载,那不能改变程序的功能吗?如果是这样,这是否意味着程序员有责任确保=
总是做与复制构造函数相同的事情?我讨厌运行这个长串的问题,但是如果是这样,为什么C++允许你定义复制构造函数来做除赋值以外的事情?
然后,我希望通过赋值复制临时值
(这可能会过载,理论上可能会有任何类型的
功能!)转换到var中,该变量以前已经初始化过
通过默认构造函数
首先,这是完全错误的
int x = 0;
int x(0);
这两行是同一件事——构造函数调用。有一些区别-我相信第一个不能调用显式构造函数-但它们是相同的函数调用。没有默认构造,也没有赋值运算符调用。它们都是直接结构
基本上,该标准说“如果您在复制构造函数中复制对象以外的其他操作,这是您自己的愚蠢错误,我笑了,因为当优化器消除调用时,您的程序没有表现出预期的行为”。当然,这些是我自己解释的话,但标准非常明确,允许优化器消除副本。在C++0x中,这也适用于移动
您上面的代码片段确实是
returnType function() { stuff....}
void someOtherFunction()
{
returnType var(function());
more stuff...
}
这不是优化的版本,这才是真正的版本。永远不会调用赋值运算符。有了NRVO,它看起来像
void function(void* mem) { // construct return value into mem
new (mem) returnType;
// Do shiz with returnType;
}
void someOtherFunction() {
// This doesn't respect some other things like alignment
// but it's the basic idea
char someMemory[sizeof(returnType)];
function(someMemory);
// more stuff here
}
当然,编译器还必须处理即使在出现异常的情况下也要销毁对象的问题,并确保所有别名的类型、对齐方式以及其他一些我在示例中没有涉及的问题,但希望您能了解一般要点。回答最后几个问题:
。。。如果赋值运算符重载,这不能更改
程序的功能?如果是,这是否意味着
程序员确保=始终执行相同操作的责任
作为复制构造函数的东西?我不喜欢经营这条长长的链条
问题,但是如果是这样,为什么C++允许你定义拷贝
构造器要做的不是赋值
是的,赋值运算符实际上可以更改程序的功能。但是赋值操作符完全有可能执行与复制构造函数不同的操作,但仍然具有相同的可观察行为。例如,对于字符串
类,I可以根据复制构造函数定义赋值运算符:
class String
{
public:
String(const wchar_t* str) : buffer(str) {}
String(const String& rhs) : buffer(rhs.buffer) {}
String& operator=(String rhs) // copy-and-swap idiom
{
Swap(rhs);
return *this;
}
// ...
void Swap(String& rhs)
{
buffer.Swap(rhs.buffer);
}
private:
// StringBuffer is an RAII wrapper for a string character array
// allocated on the free store
StringBuffer buffer;
};
在上面的代码中,复制构造函数和赋值操作符基本上做相同的事情。但是如果接收的字符串
有足够的内存来保存源字符串,那么丢弃它而不是简单地覆盖现有内容是相当浪费的
String& operator=(const String& rhs)
{
// We don't have enough room to hold the source string.
// Copy over to a new, bigger buffer.
if(rhs.buffer.Length() > buffer.Length())
{
String temp(rhs);
Swap(temp);
// temp holds our old buffer now, and will be destroyed
// when we exit this scope thanks to RAII.
}
else
{
// Instead of throwing away our existing buffer and having
// to allocate a new one, let's just overwrite what we
// have since our buffer is big enough.
}
}
显然,分配给字符串
涉及的代码与构建字符串
的副本完全不同,但仍然具有相同的可观察行为(现在我们有两个合法的字符串副本)。不同之处在于,这个更复杂的字符串赋值运算符可以避免执行超出必要的内存分配
<>如果C++语言强迫你复制复制构造函数和赋值操作符做完全相同的事情,我就不能进行这种优化。是的,我负责确保复制构造函数和赋值操作符按照您认为的那样执行,但对于所有其他操作符/特殊成员函数来说,这都是正确的。如果通过操作符=
复制对象有效,但复制构造函数不起作用(或者至少没有相同的语义),反正你也有问题。C++允许你用几种方法射击你自己的脚。能够让赋值操作符做奇怪的事情只是其中之一。赋值和复制构造是特殊的操作。如果它们没有按照预期正确实现,那么您的程序将爆炸。t var=something
要求不具有非显式构造函数以及可见副本构造函数。让我们假设,作为一个学术练习,我在=运算符重载函数中加入了某种疯狂的功能,但不是复制到构造函数中。你是说如果我写returnType=function()
,那么=
操作符函数中的代码可能永远不会执行,因为编译器可能会执行returnType var(function())
?但是,此外,这是否真的完成取决于编译器和优化选项?“我理解得对吗?”格雷维说