Подтвердить что ты не робот

С++ Вопрос о оптимизации возвращаемого значения

На этом сайте есть много вопросов о оптимизации возвращаемого значения (я полагаю, это довольно запутанная тема), но я не могу найти ответ, который отвечает на мой конкретный вопрос. Если у меня есть код, который выглядит так:

returnType function() { stuff....}

void someOtherFunction()
{
    returnType var = function();
    more stuff...
}

Мне сказали, что компилятор может решить не использовать два экземпляра returnType в someOtherFunction(). Логически, я ожидал бы, что function() будет генерировать объект типа returnType, а someOtherFunction() получит это значение через конструктор копирования (перегруженный или нет) во временное значение. Тогда я ожидал, что временное значение, которое будет скопировано с помощью присваивания (которое может быть перегружено и теоретически может иметь любую функциональность!) В var, которое ранее было бы инициализировано с помощью конструктора по умолчанию.

Я вижу здесь потенциальную проблему. Что произойдет, если эта временная копия returnType в someOtherFunction() отсутствует? Не нужно ли var заполнять через конструктор копирования возвращаемое значение из function() напрямую? Если это так, не будет ли вызов оператора назначения никогда не называться? Если это так, и если оператор присваивания перегружен, не может ли это изменить функциональность программы? Если это так, означает ли это, что ответственность программиста за то, чтобы = всегда выполняла то же самое, что и конструктор копирования? И мне не нравится запускать эту длинную цепочку вопросов, но если да, то почему С++ позволяет вам определять конструкторы копирования для выполнения чего-то другого, кроме назначения?

4b9b3361

Ответ 1

Тогда я ожидал, что временное значение будет скопировано с помощью присвоения (которые могут быть перегружены и теоретически могут иметь любые функциональность!) в var, которая ранее была инициализирована через конструктор по умолчанию.

Ну, для начала, это совершенно неправильно.

int x = 0;
int x(0);

Эти две строки - одно и то же - вызов конструктора. Есть некоторые отличия: во-первых, я не могу назвать явные конструкторы, но они являются одним и тем же вызовом функции. Конструкция по умолчанию отсутствует и нет вызова оператора присваивания. Они являются прямыми конструкциями.

В принципе, в стандарте говорится: "Если вы делаете что-то другое, кроме копирования объекта в конструкторе копирования, это ваша собственная немая ошибка, и я смеюсь, поскольку ваша программа не проявляет ожидаемого поведения, когда оптимизатор устраняет вызовы". Конечно, это мои собственные перефразируемые слова, но в Стандарте очень ясно, что оптимизатору разрешено исключать копии. В С++ 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 the basic idea
    char someMemory[sizeof(returnType)];
    function(someMemory);
    // more stuff here
}

Конечно, компилятор также должен иметь дело с разрушением объекта даже в случае исключения и убедиться, что все псевдонимы имеют правильный тип и выравнивание, а также некоторые другие вещи, с которыми я не сталкивался в своих образец, но, надеюсь, вы получите общий смысл.

Ответ 2

Чтобы ответить на несколько последних вопросов:

... если оператор присваивания перегружен, не может это изменить функциональность программы? Если это так, значит ли это, что Ответственность программиста за то, что = всегда делает то же самое как конструктор копирования? И мне не нравится запускать эту длинную цепочку вопросы, но если да, то почему С++ позволяет вам определить копию конструкторы делать что-то другое, кроме назначения?

Да, оператор присваивания может фактически изменить функциональность программы. Но вполне возможно, что оператор присваивания может сделать что-то отличное от конструктора копирования и все же иметь такое же наблюдаемое поведение. Например, для класса String я могу определить оператор присваивания в терминах конструктора копирования:

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 достаточно памяти для хранения исходной строки, довольно расточительно выбрасывать ее, а не просто переписывать существующее содержимое.

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 just overwrite what we
        // have since our buffer is big enough.
    }
}

Очевидно, что присвоение String включает совершенно другой код из копирования-построения a String, но все же имеет такое же наблюдаемое поведение (теперь у нас есть две законные копии строки). Разница в том, что этот более сложный оператор присваивания String может избежать необходимости делать больше распределений памяти, чем необходимо.

Если язык С++ заставил вас сделать оператор-конструктор и оператор присваивания одним и тем же, я бы не смог сделать такую ​​оптимизацию. Да, я отвечаю за то, чтобы оператор-копир и оператор присваивания выполняли то, что вы думаете, но это верно для всех других операторов/специальных функций-членов в любом случае.