Почему «частичное RVO» не выполняется? - PullRequest
6 голосов
/ 29 мая 2019

Пожалуйста, взгляните на эту глупую функцию, которая должна только проиллюстрировать проблему и упрощение реального кода:

struct A;

A create(bool first){
        A f(21), s(42);
        if(first)
           return f;
        else
           return s;
}

Я понимаю, потому что не ясно, какой объект будет возвращен во времяПри компиляции мы не можем ожидать, что оптимизация возвращаемого значения (RVO) будет выполняться всегда.

Однако можно ожидать, что RVO будет выполняться в 50% случаев (при условии равномерного распределения для true / * 1007).* из-за отсутствия дополнительной информации): просто решите, для какого случая следует выполнить RVO (first==true или first==false), и примените его к этому значению параметра, принимая, что в другом случае должен быть вызван конструктор копирования.

Тем не менее, это «частичное RVO» подходит не для всех компиляторов, с которыми я могу справиться (см. Прямую трансляцию с gcc , clang и MSVC ) - в обоих случаях (то есть first==true или first==false) используется конструктор копирования, а не опускается.

Есть ли что-то, что делает "частичное RVO" в приведенном выше случае недействительнымid или это маловероятный случай пропущенной оптимизации всеми компиляторами?


Полная программа:

#include <iostream>

struct A{
    int val;
    A(int val_):val(val_){}
    A(const A&o):val(o.val){
        std::cout<<"copying: "<<val<<"\n";
    }
};

A create(bool first){
        A f(21), s(42);
        if(first)
           return f;
        else
           return s;
}

int main(){
    std::cout<<"With true: ";
    create(true);
    std::cout<<"With false: ";
    create(false);
}

Ответы [ 2 ]

10 голосов
/ 29 мая 2019

Давайте рассмотрим, что произойдет, если RVO выполнено для f, что означает, что оно построено непосредственно в возвращаемом значении. Если first==true и f будут возвращены, отлично, копия не нужна. Но если first==false, тогда возвращается s, поэтому программа скопирует конструкт s поверх f до того, как деструктор для f запустится. Затем после этого будет запущен деструктор для f, и теперь возвращаемое значение является недействительным объектом, который уже был уничтожен!

Если RVO выполняется для s, вместо этого применяется тот же аргумент, за исключением того, что теперь проблема возникает, когда first==true.

Какой бы вариант вы ни выбрали, вы избегаете копирования в 50% случаев и получаете неопределенное поведение в остальных 50% случаев! Это не желательная оптимизация!

Чтобы выполнить эту работу, необходимо изменить порядок уничтожения локальных переменных, чтобы f уничтожалось до копирования s в эту область памяти (или наоборот), и это очень рискованная вещь, с которой можно связываться. Порядок уничтожения является фундаментальным свойством C ++, с которым не следует возиться, иначе вы нарушите RAII, и кто знает, сколько еще предположений.

1 голос
/ 29 мая 2019

Мое предположение, кроме того, что я с интересом прочитал ответ Джонатана Уэйкли, заключается в том, что всегда можно определить конструктор перемещения для возвращаемого объекта.Это тогда будет предпочтительнее, чем конструктор копирования, если RVO не может быть применен по какой-либо причине, и мне кажется, что это хорошее решение.

Такие вещи, как std::vector, определяют такой конструктор, так что вы получите его бесплатно.

...