1 Востаннє редагувалося leofun01 (07.06.2016 10:58:46)

Тема: std::move<T>(T &&). Як правильно його використовувати ? + const_cast

Думав що знаю C++, виявилось нічого я не знаю, принаймні не C++11.
[Конструктори і оператори] [копіювання і переміщення] наче освоїв, але є проблема з розумінням функції std::move. Я не знаю як вона працює і де потрібно її використовувати.
На stackoverflow читав, що std::move взагалі не функція, а якась спеціальна штука для компілятора. Перечитав купу інфи з різних джерел, почерпнув різні приклади його застосування (де його можна писати в коді), але це не прибавило розуміння того, де він необхідний, а де зайвий.
Хочу розглянути його на конкретному прикладі:

// Node.h
#ifndef NODE_H
#define NODE_H

template<typename T>
struct Node
{
private:
    Node<T> *Next;

public:
    T Data;

    virtual Node<T> *GetNext() { return Next; }
    virtual void SetNext(Node<T> *const next) { Next = next; }

    Node() : Data(), Next(NULL) { }
    Node(T const &data) : Data(data), Next(NULL) { }
    Node(T const &data, Node<T> *const next) : Data(data), Next(next) { }
    Node(Node<T> const &lvalue) : Data(lvalue.Data), Next(lvalue.Next) { }

    // рядки, по яких є питання:
    Node(T &&data) : Data(std::move(data)), Next(NULL) { }
    Node(Node<T> &&rvalue) : Data(std::move<T &>(rvalue.Data)),
        Next(rvalue.Next == &rvalue ? this : std::move<Node<T> *&>(rvalue.Next)) { }
    virtual ~Node() { Next = NULL; }
    // VS2008 does not support rvalue references (&&).
};
#endif // end for #ifndef NODE_H.

Чи добре таке використання std::move в рядках 23-25 ?
Чи варто явно вказувати типи-параметри функції std::move ?
Які є переваги або недоліки у такому коді в порівнянні з наступним ?

    Node(T &&data) : Data(data), Next(NULL) { }
    Node(Node<T> &&rvalue) : Data(rvalue.Data),
        Next(rvalue.Next == &rvalue ? this : rvalue.Next) { }

Буду радий будь-якій корисній інформації, яка стосується std::move, std::forward і їм подібних.
Також можете наводити свої приклади.

2 Востаннє редагувалося Yola (06.06.2016 14:23:26)

Re: std::move<T>(T &&). Як правильно його використовувати ? + const_cast

Ця пара рядків чудово показує для чого потрібен move.

    Node(T const &data) : Data(data), Next(NULL) { }
    Node(T &&data) : Data(std::move(data)), Next(NULL) { }

а оце мені не зрозуміло, навіщо тут у другому рядку move для вказівника? Це ж 4 чи 8 байтів.

    Node(Node<T> &&rvalue) : Data(std::move<T &>(rvalue.Data)),
        Next(rvalue.Next == &rvalue ? this : std::move<Node<T> *&>(rvalue.Next)) { }
leofun01 написав:

Які є переваги або недоліки у такому коді в порівнянні з наступним ?

    Node(T &&data) : Data(data), Next(NULL) { }

у наступного не буде мува:) Тому що тут data це вже lvalue. Можна використати std::forward.

Навіть якщо тип змінної є rvalue посилання, вираз що складається з його імені є lvalue-виразом.

Для дальшого вивчення std::move про досконалу переадресацію можна почитати тут

Подякували: leofun011

3

Re: std::move<T>(T &&). Як правильно його використовувати ? + const_cast

Yola написав:

а оце мені не зрозуміло, навіщо тут у другому рядку move для вказівника? Це ж 4 чи 8 байтів.

    Node(Node<T> &&rvalue) : Data(std::move<T &>(rvalue.Data)),
        Next(rvalue.Next == &rvalue ? this : std::move<Node<T> *&>(rvalue.Next)) { }

Тобто для даних маленького розміру std::move зайвий і з ним програма буде працювати повільніше ніж без нього ?

Yola написав:

Навіть якщо тип змінної є rvalue посилання, вираз що складається з його імені є lvalue-виразом.

Це мабуть ключовий момент на шляху до розуміння rvalue і lvalue. Тепер мені ясно, чому компілятор видавав success там де я очікував fatal error.

4

Re: std::move<T>(T &&). Як правильно його використовувати ? + const_cast

leofun01 написав:

Тобто для даних маленького розміру std::move зайвий і з ним програма буде працювати повільніше ніж без нього ?

Працюватиме так само швидко. std::move це шаблонна функція, яка нічого не виконує окрім перетворення типів. Тому ніяких наслідків для швидкодії не буде. Мета цього приведення типу дати можливість спрацювати мув-конструктору. До речі, якщо у вас його немає, тобто ви його не визначили або компілятор його не згенерував (наприклад, у типа є визначений вами деструктор), то відпрацює звичайний конструктор.

Подякували: leofun011

5 Востаннє редагувалося leofun01 (06.06.2016 18:15:48)

Re: std::move<T>(T &&). Як правильно його використовувати ? + const_cast

Дякую, тепер наче зрозуміло.

Можливо трохи не в тему, але ...
Захотілось мені в ту саму структуру Node добавити конструктор з сигнатурою:

Node(T const &data, Node<T> const &next)

І в мене є вибір як його зробити. Так:

Node(T const &data, Node<T> const &next)
    : Data(data), Next(const_cast<Node<T> *>(&next)) { }

чи так:

Node(T const &data, Node<T> const &next)
    : Data(data), Next(&(const_cast<Node<T> &>(next))) { }

чи ще якось інакше ?
Тобто спочатку взяти адресу і потім зняти const, чи спочатку зняти const і потім взяти адресу.
Чи вони працюватимуть однаково швидко ?

чи так взагалі робити не варто ?

6

Re: std::move<T>(T &&). Як правильно його використовувати ? + const_cast

З const_cast потрібно обережно, я не знаю де він може бути потрібен взагалі. Якщо дуже треба, то можна використати mutable для кешів чи об'єктів синхронізації (м'ютекси тощо).

У вашому випадку, краще вимагати не константне посилання. І нехай користувач вже піклується, щоб дизайн його програми дозволяв йому скористатись вашим класом. І це трошки спантеличує коли бачиш імена членів даних з великої літери:) так в тексті Data або Next від Node не відрізниш одразу.

Подякували: leofun011

7 Востаннє редагувалося leofun01 (07.06.2016 11:02:20)

Re: std::move<T>(T &&). Як правильно його використовувати ? + const_cast

leofun01 написав:

Так:

Node(T const &data, Node<T> const &next)
    : Data(data), Next(const_cast<Node<T> *>(&next)) { }

чи так:

Node(T const &data, Node<T> const &next)
    : Data(data), Next(&(const_cast<Node<T> &>(next))) { }

Прочитав трохи cppreference і geeksforgeeks про const_cast.
Дійшов до висновку, що в моєму випадку таких конструкторів краще не робити.
const_cast можна використовувати тільки у випадках, коли є гарантія, що const агрумент не буде справжнім const, інакше може бути неочікувана поведінка компілятора або програми.

Yola написав:

У вашому випадку, краще вимагати не константне посилання.

+1

Подякували: Yola1