Re[6]: Передать массив структур из одного класса в другой
От: .alex Ниоткуда  
Дата: 08.12.20 15:52
Оценка:
Здравствуйте, watchmaker, Вы писали:

Спасибо! Есть что поизучать...
Еще один глупый вопрос, получается std::move() правильно писать и при вызове метода и внутри этого метода, так?
#include <iostream>
#include <vector>
#include <Windows.h>

struct Aaa
{
    char sz[32];
    int a;
    std::string s;

    Aaa(const char* sz_, int a_, std::string str) : a(a_), s(std::move(str))
    {
        strcpy_s(sz, sz_);
    }
};

class cl2
{
public:
    void Foo2(std::vector<Aaa>&& arg)
    {
        std::vector<Aaa> local = std::move(arg);
        for (const auto& a : local)
        {
            std::cout << a.sz << "\t" << a.a << "\t" << a.s << std::endl;
        }
        std::cout << std::endl;
    }
};
cl2 c2;

class cl1
{
public:
    std::vector<Aaa> vAaa;

    void Foo1()
    {
        
        for (int i = 0; i < 500; i++)
        {
            int nCnt = 10 + i;
            vAaa.reserve(nCnt);

            for (int j = 0; j < nCnt; j++)
            {
                vAaa.emplace_back("sz", j, "string");
            }
            c2.Foo2(std::move(vAaa));
        }
    }
};

void main()
{
    DWORD dw = GetTickCount();
    cl1 c1;
    c1.Foo1();
    std::cout << GetTickCount() - dw << std::endl;
}
Re[7]: Передать массив структур из одного класса в другой
От: watchmaker  
Дата: 08.12.20 18:06
Оценка: 3 (1)
Здравствуйте, .alex, Вы писали:

A>Здравствуйте, watchmaker, Вы писали:



A> вопрос, получается std::move() правильно писать и при вызове метода и внутри этого метода, так?

Ну в целом да. Если хочешь передать владение, то нужно писать move. Если хочешь это сделать по цепочке, то аннотировать так придётся каждый шаг.
(разумеется, тут есть варианты, когда цепочка функций принимает lvalue-ссылки, но по их названию понятно, что они будут делать в конце похищение ресурсов, тогда можно как бы и без постоянного повторения move код написать, но это всё не про подход, который нужно использовать по умолчанию).


---------------------------------------
Ну и заодно ещё скажу, что в этом фрагменте кода есть проблема:
A>        std::vector<Aaa> vAaa;
A>        for (int i = 0; i < 500; i++)
A>        {
A>            int nCnt = 10 + i;
A>            vAaa.reserve(nCnt);

A>            for (int j = 0; j < nCnt; j++)
A>            {
A>                vAaa.emplace_back("sz", j, "string");
A>            }
A>            c2.Foo2(std::move(vAaa));
A>        }


Тут подразумевается, что в начале каждой итерации внешнего цикла массив vAaa пуст.
Но мне кажется, что тут скорее случайно у тебя так получилось
И этот код развалится, если чуть-чуть поменять тело Foo2, например.

Дело в том, что в С++ объекты, из которых сделали перемещение, обычно остаются в "valid but unspecified state": https://en.cppreference.com/w/cpp/utility/move
И к стандартным контейнерам это тоже относится.

То есть очень неправильно полагаться на то, что после вызова c2.Foo2(std::move(vAaa)); вектор vAaa опустеет. Это как раз может быть не так. И есть много сценариев, когда он после этого может оказаться заполненным какими-то объектами из предыдущих итераций или типа того. Что конкретно произойдёт зависит от внутреннего устройства функции Foo2. И это как раз проблема, так как в этой точке его не видно, и оно может меняться без изменения сигнатуры. Короче говоря, в будущем тут легко может реализоваться баг при редактировании каких-то совсем других функций, который вызываются из Foo2.


Поэтому, если тебе хочется использовать контейнер, из которого сделали move, то нужно его перевести из "unspecified state" в собственно "specified state". Для вектора это означает, что нужно вызвать в начале каждой итерации vAaa.clear()(и потом уже vAaa.reserve(nCnt) и прочее).

Хотя, конечно, ещё и непонятно зачем ты тут используешь vAaa общий для класса, а не создаёшь локальный в функции. Но, возможно, это издержки примера и в реальном коде есть какая-то веская причина (потому что в текущем примере её нет ).
Re[8]: Передать массив структур из одного класса в другой
От: .alex Ниоткуда  
Дата: 09.12.20 07:20
Оценка:
W>Тут подразумевается, что в начале каждой итерации внешнего цикла массив vAaa пуст.
W>Но мне кажется, что тут скорее случайно у тебя так получилось
W>И этот код развалится, если чуть-чуть поменять тело Foo2, например.
W>Дело в том, что в С++ объекты, из которых сделали перемещение, обычно остаются в "valid but unspecified state": https://en.cppreference.com/w/cpp/utility/move
W>И к стандартным контейнерам это тоже относится.
W>То есть очень неправильно полагаться на то, что после вызова c2.Foo2(std::move(vAaa)); вектор vAaa опустеет. Это как раз может быть не так. И есть много сценариев, когда он после этого может оказаться заполненным какими-то объектами из предыдущих итераций или типа того. Что конкретно произойдёт зависит от внутреннего устройства функции Foo2. И это как раз проблема, так как в этой точке его не видно, и оно может меняться без изменения сигнатуры. Короче говоря, в будущем тут легко может реализоваться баг при редактировании каких-то совсем других функций, который вызываются из Foo2.
W>Поэтому, если тебе хочется использовать контейнер, из которого сделали move, то нужно его перевести из "unspecified state" в собственно "specified state". Для вектора это означает, что нужно вызвать в начале каждой итерации vAaa.clear()(и потом уже vAaa.reserve(nCnt) и прочее).
Да, я обратил на это внимание и как раз поэтому спрашивал где нужно писать std::move(), т.к. если я писал std::move() только в c2.Foo2(std::move(vAaa)); и не писал его самом методе при std::vector<Aaa> local = arg;, то в вектор у меня не очищался... Ну а если пишу два раза и при вызове и при присваивании или просто при присваивании, то все было ок — очищался... Ладно буду писать std::move() везде и делать clear перед reserve на каждой итерации... (однако, опять хочется сказать, что в Си попроще с указателями))

W>Хотя, конечно, ещё и непонятно зачем ты тут используешь vAaa общий для класса, а не создаёшь локальный в функции. Но, возможно, это издержки примера и в реальном коде есть какая-то веская причина (потому что в текущем примере её нет ).

В реальном коде этот вектор содержит результат рекордсета из БД и отображается в виртуальном лист контроле (поэтому и общий для класса), а как нажали кнопку — уезжает в другой класс для обработки, а на его место помещается новый результат запроса из БД и также отображается и потом уезжает...
Re[6]: Передать массив структур из одного класса в другой
От: .alex Ниоткуда  
Дата: 09.12.20 07:32
Оценка:
W>Но вывод такой:
W>Если класс имеет дешёвый move-конструктор, то можно писать так:
W>
W>Aaa(std::string str) : s(std::move(str)) 
W>

W>Если класс имеет дорогой move-конструктор, то лучше писать так:
W>
W>Aaa(const std::string& str) : s(str) 
W>Aaa(std::string&& str) : s(std::move(str)) 
W>// или шаблонный аналог вместо двух вариантов
W>

А как определить что такое дорогой move конструктор?
В моем представлении если например вектор, который я хочу перекинуть в другое место занимает мегов 500 в памяти, то дорого для него будет копироваться, а move это просто передача указателя на него, как в Си (т.е. ничего нового выделять не нужно, а нужно просто передать 4(8) байт указателя) или опять я упрощаю? ))
т.е. move всегда будет быстрее copy...
Re[7]: Передать массив структур из одного класса в другой
От: qaz77  
Дата: 09.12.20 09:23
Оценка: 4 (2) +1
Здравствуйте, .alex, Вы писали:
A>А как определить что такое дорогой move конструктор?

Если в классе много данных непосредственно, а не по указателям, то move-конструктор вынужден их просто скопировать.

Например, такой класс:
class Bar
{
  int memb0_;
  //...
  int memb99_;

  char text_[200];
  double coef_[30];

  std::vector<std::string> names_;

  // ...
};

Тут только вектор дешево переместится, а инты и массивы будут скопированы — вариантов нет.

Для всех стандартных контейнеров перемещение относительно дешевое.
У вектора — пара указателей и capacity. Примерно можно оценить по sizeof класса.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.