Форум программистов, компьютерный форум, киберфорум
soon
Войти
Регистрация
Восстановить пароль
Рейтинг: 5.00. Голосов: 3.

Семантика перемещения и perfect forwarding(правильная передача)

Запись от soon размещена 18.10.2012 в 19:14
Обновил(-а) soon 27.10.2012 в 22:46

По стандарту С++, временный объект можно передавать в функцию только по константной ссылке. Это вызывало некоторые осложнения, поскольку временный объект был неотличим от константного объекта, как следствие, было неясно, надо-ли сохранять объект или можно просто его просто переместить. Стандарт C++11 представляет новый вид ссылки - rvalue reference - ссылка на временный объект. Как и обычная ссылка, она может быть const и non-const. Как и обычная ссылка, она должна быть инициализирована при объявлении.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int foo()
{
    int a = 42;
    return a;
}
 
int main()
{
    const int&& a = foo();
    // a = foo(); // Error
 
    int&& b = foo();
    b = foo();
    return 0;
}
Вернемся чуть позже к вопросу о возвращаемом типе.

std::move()
C++
1
2
3
4
5
template <class T>
std::remove_reference<T>::type&& move(T&& t)
{
    return static_cast<typename std::remove_reference<T>::type&&>(t)
}
Как видим, std::move не делает ничего, кроме преобразования типа. В этом и заключается особенность перемещения - мы лишь сигнализируем о том, что данные в объекте нам больше не нужны. Взглянем на пример
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Foo
{
    int* _arr;
 
public:
    Foo()
    {
        _arr = new int[1024];
    }
 
    Foo(Foo&& f): _arr(std::move(f._arr))
    {
 
    }
 
    ~Foo()
    {
        delete[] _arr;
    }
};
 
int main()
{
    Foo f1;
    Foo f2(std::move(f1));
    return 0;
}
Опа! Glibc detected. Все дело в том, что f1._arr и f2._arr указывают на одну область памяти. Итак, что я пытался этим донести. Первое, std::move только возвращает rvalue reference на переданный аргумент. Ничего не стирается. Второе, если явно выделяется память, то нужно проследить за корректным ее освобождением. Пример выше правится добавлением
C++
1
f._arr = nullptr;
в перемещающий конструктор.

Теперь к вопросу о возвращаемом типе. Как уже было сказано, rvalue reference по-прежнему ссылка. Следовательно, никогда не возвращайте rvalue reference на локальную переменную. Стандарт гарантирует вызов перемещающего конструктора, если он определен и нет оптимизаций со стороны компилятора. В противном случае, будет вызван копирующий конструктор, либо, при его отсутствии, будет ошибка времени компиляции.

Также следует помнить, что даже при передаче rvalue reference в копирующий конструктор(к примеру), все его члены(исключение, пожалуй, могут составить примитивные типы, поскольку разницы между копированием / перемещением там нет) необходимо явно приводить к rvalue reference, чтобы не был вызван конструктор копирования. Это касается, в том числе, передачи полученного аргумента в другие функции. Объясняется это тем, что за rvalue reference скрываются реально существующие данные, которые абсолютно точно уверены в том, что они lvalue, а следовательно, и ведут себя как lvalue. Однако, бывают ситуации, когда мы не можем утверждать, что передан T&&, а не rvalue reference на T&. Да-да, шаблонная "магия"

Perfect Forwarding

Взглянем на код
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void foo(const int&)
{
    std::cout << "Hello! I'm foo(const int&)" << std::endl;
}
 
void foo(int&&)
{
    std::cout << "Hello! I'm foo(int&&)" << std::endl;
}
 
template <class T>
void bar(T&& t)
{
    std::cout << "Hello! I'm bar<T>(T&&)" << std::endl;
    foo(std::move(t));
}
 
int main()
{
    bar(42);
 
    int a = 42;
    bar(a);
 
    return 0;
}
Как я уже сказал выше, во втором случае шаблон инстанцируется с типом rvalue reference на reference. В этом случае на сцену выходит std::forward<T>. Его функции просты - передать объект далее, сохраняя его тип. Если переписать функцию bar(T&&), то мы получим то, что и ожидали увидеть с самого начала.
C++
1
2
3
4
5
6
template <class T>
void bar(T&& t)
{
    std::cout << "Hello! I'm bar<T>(T&&)" << std::endl;
    foo(std::forward<T>(t));
}
Теперь, при вызове bar<int&> будет вызвана функция foo(const int&), а при вызове bar<int> - foo(int&&)
Размещено в Без категории
Просмотров 6530 Комментарии 4
Всего комментариев 4
Комментарии
  1. Старый комментарий
    Аватар для alex_x_x
    Чето в меня тяжело ходят rvalue reference'ым (а в стандарте их целая разновидность)

    Цитата:
    Как видим, std::move не делает ничего, кроме преобразования типа. В этом и заключается особенность перемещения - мы лишь сигнализируем о том, что данные в объекте нам больше не нужны. Взглянем на пример
    и происходит это на момент компиляции как понимаю
    Запись от alex_x_x размещена 20.10.2012 в 15:59 alex_x_x вне форума
  2. Старый комментарий
    Аватар для soon
    alex_x_x, поясните, что за разновидности rvalue reference?
    Запись от soon размещена 20.10.2012 в 17:23 soon вне форума
  3. Старый комментарий
    Аватар для alex_x_x
    Цитата:
    дарт С++11 объявляет (3.10) пять терминов: lvalue, rvalue, xvalue, а также glvalue (обобщение lvalue и xvalue) и prvalue (более узкая специализация rvalue). Базовыми терминами являются знакомые нам lvalue, rvalue и новый термин xvalue (от expiring value). Забегая вперёд – например, xvalue является результатом функции, возвращающей rvalue ссылку. То, что понималось под rvalue в C++98, в C++11 стало prvalue (в то время как rvalue обобщает xvalue и prvalue), а новый термин glvalue обобщает lvalue и xvalue. Можно (неточно, но образно) сказать, что prvalue – это то, брать адрес от чего нельзя, lvalue – от чего можно, а xvalue – от чего бесполезно.
    http://img198.imageshack.us/im... 516966.png
    Запись от alex_x_x размещена 20.10.2012 в 18:31 alex_x_x вне форума
  4. Старый комментарий
    Аватар для soon
    А, вот вы о чем. Я думал, включать или нет это в статью. Оригинал на SO. Если требуется, я могу перевести.
    Запись от soon размещена 20.10.2012 в 18:59 soon вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2021, vBulletin Solutions, Inc.