Как я могу обеспечить выполнение RVO вместо копирования? - PullRequest
3 голосов
/ 05 марта 2020

Во многих случаях я хотел бы создать новый экземпляр данных и вернуть его вызывающей стороне API.

Я узнал, что unique_ptr / shared_ptr можно использовать для заводского шаблона (например, Заводской шаблон с использованием unique_ptr в c ++ )

В то же время, Я узнал, что оптимизация возвращаемого значения (RVO) возможна во многих компиляторах (например, Эффективный способ вернуть std :: vector в c ++ ).

Я предпочитаю RVO, поскольку легче использовать возвращаемое значение без переноса unique_ptr и легче читать код, однако, поскольку RVO не гарантируется, я не хочу неожиданно жертвовать производительностью и необходимо использовать unique_ptr, чтобы убедиться, что возвращаемое значение равно move d, а не скопировано.

Существует ли какой-либо подход, в котором я могу явно указать возвращаемое значение для перемещения, чтобы либо он не жаловался на что-либо, если RVO возможен, либо он вызывал бы некоторое предупреждение компилятора, если RVO невозможен? Если это возможно, я могу смело избавиться от возврата unique_ptr в этом случае.

Я использую C ++ 17 и мне нужно поддерживать Apple Clang 11.0 на macOS и g ++ 9 на Linux.

Отредактировано:

Я все еще изучаю C ++ и не делал различий между RVO (оптимизация возвращаемого значения) и NRVO (оптимизация именованного возвращаемого значения) при публикации этого вопроса. Мне кажется, NRVO более распространен и полезен в шаблонах, таких как фабричный метод, например:

vector<foo> vec;
// populate data into vec
return vec;

И я ищу что-то вроде return std::move_only(returned_value), которое выдаст мне предупреждение компилятора, если это значение не может быть перемещенным (не копировать для перемещения). Возможно, мне следует перефразировать мой вопрос следующим образом: если NRVO не гарантируется, почему «возвращение по значению» все еще является рекомендуемым способом в этом вопросе ( Эффективный способ вернуть std :: vector в c ++ ), не должен ли ответ быть «это зависит» от реализации вашей функции и от того, согласны ли вы с неожиданными затратами на производительность?

Ответы [ 2 ]

4 голосов
/ 05 марта 2020

Как я могу обеспечить выполнение RVO вместо копирования?

Язык уже делает это для вас, начиная с C ++ 17. Если у вас есть конструкция типа

T foo() { /*stuff*/; return T{ /*stuff*/ }; }

, то возвращаемый объект гарантированно будет удален благодаря гарантированному разрешению копирования .

Если у вас есть конструкция, подобная

T foo() 
{
    T obj{ /*stuff*/ }; 
    // do stuff with obj
    return obj;
}

Тогда вы либо получите NRVO (Nammed Return Value Optimization), который не гарантирован, либо компилятор переместится obj, поскольку в стандарте есть правило, согласно которому все функциональные локальные объекты с автоматом c продолжительность хранения будет удалена из функции, если у них есть конструктор перемещения.

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

1 голос
/ 05 марта 2020

Я предпочитаю RVO, поскольку проще использовать возвращаемое значение без переноса unique_ptr

Вы не можете вернуть unique_ptr без RVO, NRVO или неявного перемещения в случае, если NRVO isn ' т возможно. Это не копируется:

std::unique_ptr<int> ptr1;
std::unique_ptr<int> ptr2;
ptr2 = ptr1; // error: not copyable

Это не компилируется. Если бы не RVO, NRVO или перемещение, это не скомпилировало бы либо:

std::unique_ptr<int> foo()
{
    return std::unique_ptr<int>{};
}

В этом случае это происходит из-за гарантированного RVO в C ++ 17. Но даже если бы не было RVO, вы бы все равно получили ход вместо копии.

И если бы не NRVO или гарантированный откат перемещения, это не скомпилировало бы:

std::unique_ptr<int> foo()
{
    std::unique_ptr<int> ptr;
    return ptr;
}

То есть вы уже зависите от RVO, NRVO или ходов. Нет необходимости в unique_ptr. Если ваши типы являются подвижными, вы можете быть уверены, что копии не выполняются даже в тех случаях, когда NRVO невозможно, например, когда не все операторы return возвращают один и тот же локальный объект:

std::unique_ptr<int> foo(const bool flag)
{
    if (flag) {
        std::unique_ptr<int> ptr1;
        return ptr; // implicit move
    }
    std::unique_ptr<int> ptr2;
    return ptr2; // implicit move
}
...