прохождение rvalue против lvalue вектора - PullRequest
2 голосов
/ 08 апреля 2019

У меня есть метод my func, который получает вектор из get_vec и передает его какому-то конструктору класса A.

class A
{
public:
    A(std::vector<int>&& vec) : m_vec(std::move(vec)) {}
    std::vector<int> m_vec;
};

std::vector<int> get_vec()
{
   std::vector<int> res;
   // do something
   return res;
}

void my_func()
{
    std::vector<int> vec = get_vec();
    A(std::move(vec));
}

https://godbolt.org/z/toQ5KZ

В идеале, я бы хотел, чтобы вектор строился один раз, но в этом примере я создаю вектор в get_vec, затем копирую его в my_func, перемещаю в конструктор, а затем снова перемещаю в A::m_vec.

Как правильно и эффективно передать вектор?

Ответы [ 3 ]

3 голосов
/ 08 апреля 2019

std::vector<int>&& m_vec элемент приведет к зависанию, если вы, например, наберете A a(get_vec());.

Правильный и безопасный способ - иметь этого члена по значению:

std::vector<int> m_vec;
1 голос
/ 08 апреля 2019

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

Мы можем продемонстрировать это с помощью фиктивного вектора, особые функции-члены которого имеют побочные эффекты (например, запись в переменную volatile):

class MyVec
{
public:
    MyVec() { x = 11; };

    MyVec(const MyVec&) { x = 22; }
    MyVec(MyVec&&) { x = 33; }

    MyVec& operator=(const MyVec&) { x = 44; return *this; }
    MyVec& operator=(MyVec&&) { x = 55; return *this; }

    // Make this the same size as a std::vector
    void* a = nullptr;
    void* b = nullptr;
    void* c = nullptr;
};

Если мы проверим оптимизированную сборку , мы увидим, что только один конструктор по умолчанию и побочные эффекты одного конструктора перемещения фактически сохраняются в my_func, все остальное оптимизируется. Первый конструктор по умолчанию - встроенный get_vec, другой - перемещение в конструкторе A. Это настолько эффективно, насколько это возможно при создании члена из временного.

Это разрешено, потому что копии могут быть удалены при return из функции, а также при инициализации из (более или менее) временного. Последний имел обыкновение быть «вы можете удалить копию в X x = getX();», но, поскольку версии C ++ 17 не создают никаких временных (https://en.cppreference.com/w/cpp/language/copy_initialization).

0 голосов
/ 08 апреля 2019

Я решил переписать этот ответ, так как Хорхе Перес любезно указал, что оригинал был ошибочным, и потому что ни один из других ответов на самом деле не касается оригинального вопроса.

Я начал с написания простого тестаПрограмма:

#include <iostream>

class Moveable
{
public:
    Moveable ()  { std::cout << "Constructor\n"; }
    ~Moveable ()  { std::cout << "Destructor\n"; }
    Moveable (const Moveable&) { std::cout << "Copy constructor\n"; }
    Moveable& operator= (const Moveable&) { std::cout << "Copy assignment\n"; return *this; }
    Moveable (const Moveable&&) { std::cout << "Move constructor\n"; }
    Moveable& operator= (const Moveable&&) { std::cout << "Move assignment\n"; return *this; }
};

class A
{
public:
    A (Moveable &&m) : m_m (std::move (m)) {}
    Moveable m_m;
};

Moveable get_m ()
{
   Moveable res;
   return res;
}

int main ()
{
    Moveable m = get_m ();
    A (std::move (m));
}

, которая дает следующий вывод:

Constructor
Move constructor
Destructor
Destructor

Таким образом, вы сразу можете увидеть, что неэффективность в вашем коде не так плоха, как вы думаете - есть только один шаг ибез копий.

Теперь, как говорили другие:

Moveable m = get_m ();

ничего не копирует из-за Оптимизация именованного возвращаемого значения (NRVO) .

И есть только один ход, потому что:

A (std::move (m));

на самом деле ничего не двигает (это просто актерский состав).

Живая демоверсия

Что касается движения, то, возможно, яснее, что происходит, если вы измените это:

A (Moveable&& m) : m_m (std::move (m)) {}

на следующее:

A (Moveable& m) : m_m (std::move (m)) {}

, потому что вы можете изменить это:

A (std::move (m));

на это:

A {m};

и все равно получите тот же результатt (вам нужны скобки, чтобы избежать «самого неприятного разбора»).

Живая демоверсия

...