Страницы

Поиск по вопросам

четверг, 5 декабря 2019 г.

Повсеместное использование r-value ссылок

#cpp #cpp11


Стоит ли повсеместно стараться как можно чаще использовать r-value ссылки? Вот, допустим, код:

std::string hi() {
    return "hello world\n"
}

auto&& str = hi();


В данном случае в строке 5 происходит лишь одно создание объекта, и r-value ссылка
на него, и никакого копирования... эффективно, если сравнить, например, с:

auto str = hi();


или

auto str = std::move(hi());


Посему на первый взгляд напрашивается вывод: почему бы не создавать r-value ссылки
как можно чаще вместо обычной инициализации переменной? Особенно если нужно создавать
100000 раз в секунду эти переменные. Допустим, если можно, то писать так:

void function() {
    int&& a = 1;
    double&& b = 2.;
    auto&& value = return_value();
    /*далее какой-то код*/
}


и далее работать с этими ссылками или передавать их в другое место... выглядит, это
чуть более монстрообразно, чем обычная инициализация переменных, к которой глаз привык:

void function() {
    int a = 1;
    double b = 2.;
    auto value = return_value();
    /*далее какой-то код*/
}


Поэтому стоит ли как можно чаще пользоваться r-value ссылками вместо обычной инициализации
новых переменных в локальном контексте(внутри функций, например) для эффективности,
или есть ли какие-то встроенные оптимизации, которые при int a = 2 делают те же действия,
что и int&& a = 2?
    


Ответы

Ответ 1



Обычно ссылки хранятся в памяти в виде указателей на объекты. Поэтому в данном предложении auto &&a = 1; будет создан временный объект со значением 1 и ссылка на него. Если у вас есть объявление вида auto a = f(); где функция f возвращает объект некоторого типа T, то этот объект, возвращаемый из функции, строится на месте переменной a , минуя вызов копирующего или перемещающего конструктора. Рассмотрите следующий пример #include struct Int { Int( int x = 0 ) : x( x ) { std::cout << "Int::Int( int )" << std::endl; } Int( const Int &rhs ) : x( rhs.x ) { std::cout << "Int::Int( const Int & )" << std::endl; } Int( Int &&rhs ) : x( rhs.x ) { std::cout << "Int::Int( Int && )" << std::endl; } ~Int() { std::cout << "Int::~Int()" << std::endl; } int x; }; Int f( int x ) { return x; } int main() { auto x = f( 10 ); auto &&y = f( 20 ); return 0; } Вывод на консоль будет следующим Int::Int( int ) Int::Int( int ) Int::~Int() Int::~Int() То есть в обоих случаях будет вызвано по одному конструктору и, соответственно, деструктору. К тому же нельзя создавать, например, массивы из ссылок. В нижеприведенной программе, если раскомментировать предложение с массивом ссылок, то будет выдано сообщение об ошибке. #include struct Int { Int( int x = 0 ) : x( x ) { std::cout << "Int::Int( int )" << std::endl; } Int( const Int &rhs ) : x( rhs.x ) { std::cout << "Int::Int( const Int & )" << std::endl; } Int( Int &&rhs ) : x( rhs.x ) { std::cout << "Int::Int( Int && )" << std::endl; } ~Int() { std::cout << "Int::~Int()" << std::endl; } int x; }; Int f( int x ) { return x; } int main() { Int a[] = { f( 10 ) }; //Int &&b[] = { f( 10 ) }; return 0; }

Ответ 2



На самом деле доступ к переменной напрямую всегда быстрее чем по ссылке. Составим простую программу: int O() { int x = 5; return x; } int test(){ auto&& value = O(); return value; } int test2(){ auto value = O(); return value; } Соберём БЕЗ оптимизаций. test(): push rbp mov rbp, rsp sub rsp, 16 call O() mov DWORD PTR [rbp-12], eax lea rax, [rbp-12] mov QWORD PTR [rbp-8], rax mov rax, QWORD PTR [rbp-8] mov eax, DWORD PTR [rax] leave ret test2(): push rbp mov rbp, rsp sub rsp, 16 call O() mov DWORD PTR [rbp-4], eax mov eax, DWORD PTR [rbp-4] leave ret Как видите работа напрямую - меньше операций сразу, хотя мы переменную и не использовали считай. В общем для примитивов однозначно лучше переменная, для сложных классов - воспользуйтесь профайлером (возможно есть смысл использовать перемещающий конструктор или что-то такое).

Ответ 3



В данном случае нет никакой практической разницы между auto str = hi(); и auto&& str = hi(); В обоих случаях copy elision и return value optimization приводят к тому, что результат функции hi будет формироваться (конструироваться) напрямую в финальном объекте. В первом случае - напрямую в str. Во-втором случае - напрямую во временном объекте, к которому будет привязываться str.

Комментариев нет:

Отправить комментарий