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

307
26 ноября 2016, 19:01

Стоит ли повсеместно стараться как можно чаще использовать 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?

Answer 1

Обычно ссылки хранятся в памяти в виде указателей на объекты. Поэтому в данном предложении

auto &&a = 1;

будет создан временный объект со значением 1 и ссылка на него.

Если у вас есть объявление вида

auto a = f();

где функция f возвращает объект некоторого типа T, то этот объект, возвращаемый из функции, строится на месте переменной a , минуя вызов копирующего или перемещающего конструктора.

Рассмотрите следующий пример

#include <iostream>
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 <iostream>
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;
}
Answer 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

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

Answer 3

В данном случае нет никакой практической разницы между

auto str = hi();

и

auto&& str = hi();

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

READ ALSO
запрет на использование system() и exec()

запрет на использование system() и exec()

Доброе время суток! У меня есть проект на Python35, а именно система автоматического тестирования для локальной сети(тестер для олимпиадных задач...

250
Как удалить элементы tooltip

Как удалить элементы tooltip

Здравствуйте! Подскажите, как удалять такие элементы:

281
Двоеточие в полях структуры

Двоеточие в полях структуры

Объясните, пожалуйста, как тут создается структура? Что делает знак двоеточие :?

250
Не получается считать строку из консоли

Не получается считать строку из консоли

Нужно считать строку из консоли в массив char

301