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())
?但是,此外,这是否真的完成取决于编译器和优化选项?“我理解得对吗?”格雷维说