Re[9]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 14.04.25 13:48
Оценка:
Здравствуйте, Shmj, Вы писали:

R>Тебе вопрос задали
Автор: _NN_
Дата: 23.03 23:12
: какую задачу ты решаешь.


S>Я ж писал — бинарный пакет с удобным доступом к частям пакета. В обе стороны — и формируем и парсим.


Так а вот здесь
Автор: Shmj
Дата: 14.04 16:00
ты пишешь, что это просто для примера. А реальная задача другая, так получается. Что-то ты в показаниях путаешься.


S>А как я могу понять что фигня, если оно работает?


Это хороший вопрос

S>Возможно что было бы лучше использовать поток а не массив байт, но пока в данном случае не получается.


Эта проблема называется "мультифакторная инициализация". Вместо того, чтобы сразу создать объект в согласованном состоянии, ты создаешь неюзабельный полуфабрикат. Потом ты из этого полуфабриката пытаешься вылепить конфетку при помощи серии модификациий. Это всё уже кручено-перекручено тысячи раз.
--
Справедливость выше закона. А человечность выше справедливости.
Re[7]: Про перемещение (на примере кода)
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 14.04.25 14:01
Оценка: :)
Здравствуйте, Shmj, Вы писали:

S>Сетевой бинарный пакет. С помощью методов устанавливаем удобным образом отдельные части пакета — как то версия, тип пакета и пр. Но так же должна быть возможность получить данные пакета целиком для отправки (или предварительного шифрования).


Знакомый делал тестовое задание, кидал мне посмотреть. Я у него разрешение спросил, он сказал, что можно опубликовать. Так что
  смотри и учись
/*
################################################################################
# Описание бинарного протокола
################################################################################

По сети ходят пакеты вида
packet : = size payload

size - размер последовательности, в количестве элементов, может быть 0.

payload - поток байтов(blob)
payload состоит из последовательности сериализованных переменных разных типов :

Описание типов и порядок их сериализации

type : = id(uint64_t) data(blob)

data : =
    IntegerType - uint64_t
    FloatType - double
    StringType - size(uint64_t) blob
    VectorType - size(uint64_t) ...(сериализованные переменные)

Все данные передаются в little endian порядке байтов

Необходимо реализовать сущности IntegerType, FloatType, StringType, VectorType
Кроме того, реализовать вспомогательную сущность Any
Сделать объект Serialisator с указанным интерфейсом.

Конструкторы(ы) типов IntegerType, FloatType и StringType должны обеспечивать инициализацию, аналогичную инициализации типов uint64_t, double и std::string соответственно.
Конструктор(ы) типа VectorType должен позволять инициализировать его списком любых из перечисленных типов(IntegerType, FloatType, StringType, VectorType) переданных как по ссылке, так и по значению.
Все указанные сигнатуры должны быть реализованы.
Сигнатуры шаблонных конструкторов условны, их можно в конечной реализации делать на усмотрение разработчика, можно вообще убрать.
Vector::push должен быть именно шаблонной функцией. Принимает любой из типов: IntegerType, FloatType, StringType, VectorType.
Serialisator::push должен быть именно шаблонной функцией.Принимает любой из типов: IntegerType, FloatType, StringType, VectorType, Any
Реализация всех шаблонных функций, должна обеспечивать constraint requirements on template arguments, при этом, использование static_assert - запрещается.
Код в функции main не подлежит изменению. Можно только добавлять дополнительные проверки.

Архитектурные требования :
1. Стаедарт - c++17
2. Запрещаются виртуальные функции.
3. Запрещается множественное и виртуальное наследование.
4. Запрещается создание каких - либо объектов в куче, в том числе с использованием умных указателей.
   Это требование не влечет за собой огранечений на использование std::vector, std::string и тп.
5. Запрещается любое дублирование кода, такие случаи должны быть строго обоснованы. Максимально использовать обобщающие сущности.
   Например, если в каждой из реализаций XType будет свой IdType getId() - это будет считаться ошибкой.
6. Запрещается хранение value_type поля на уровне XType, оно должно быть вынесено в обобщающую сущность.
7. Никаких других ограничений не накладывается, в том числе на создание дополнительных обобщающих сущностей и хелперов.
8. XType должны реализовать сериализацию и десериализацию аналогичную Any.

Пример сериализации VectorType(StringType("qwerty"), IntegerType(100500))
{0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x71,0x77,0x65,0x72,0x74,0x79,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x88,
 0x01,0x00,0x00,0x00,0x00,0x00}
*/

#include <iostream>
#include <vector>
#include <fstream>
#include <type_traits>
#include <variant>
#include <algorithm>


using Id = uint64_t;
using Buffer = std::vector<std::byte>;

enum class TypeId : Id {
    Uint,
    Float,
    String,
    Vector
};


class Any {
private: 

    using DataType = std::variant<uint64_t, double, std::string, std::vector<Any>>;
    static constexpr TypeId idToType[] = {TypeId::Uint, TypeId::Float, TypeId::String, TypeId::Vector};
    static constexpr size_t typeToId(TypeId type)
    {
        for(auto id = 0; id < std::size(idToType); id++)
        {
            if(idToType[id] == type)
            {
                return id;
            }
        }
        return -1;
    }

public:

    template<typename ...Args, std::enable_if_t<std::is_constructible_v<DataType, Args...>, bool> = false>
    Any(Args&& ... args) : m_place(std::forward<Args...>(args...))
    {}

    Any() = default;

    void serialize(Buffer& _buff) const
    {
        auto append = [&_buff](auto && val, size_t size = sizeof(val)){
            _buff.resize(_buff.size() + size);

            auto * begin = reinterpret_cast<const std::byte *>(&val);
            if constexpr (std::is_pointer_v<std::decay_t<decltype(val)>>)
            {
                begin = reinterpret_cast<const std::byte *>(val);
            }
            
            std::copy(begin, begin + size, _buff.end() - size);
        };
        auto type = getPayloadTypeId();
        append(type);
        switch (type)
        {
        case TypeId::Uint:
        {
            append(std::get<uint64_t>(m_place));
            return;
        }
        case TypeId::Float:
        {
            append(std::get<double>(m_place));
            return;
        }
        case TypeId::String:
        {
            const auto & str = std::get<std::string>(m_place);
            uint64_t stringLength = str.size();
            append(stringLength);
            append(str.data(), stringLength);

            return;
        }
        case TypeId::Vector:
        {
            const auto & vec = std::get<std::vector<Any>>(m_place);
            uint64_t vectorLength = vec.size();
            append(vectorLength);
            for(const auto & v : vec)
            {
                v.serialize(_buff);
            }
            return;
        }
        
        default:
            break;
        }
        return;
    }

    Buffer::const_iterator deserialize(Buffer::const_iterator _begin, Buffer::const_iterator _end)
    {
        // TODO: need to add actions on failed deseriasization 
        // All types takes at least 2 * sizeof(uint64_t) bytes 
        // (TypeId + value) or (TypeId + Length)
        auto hasEnoughtSpace = [&_begin, &_end](auto size){return _end - _begin >= size;};
        if(!hasEnoughtSpace(sizeof(Id) + sizeof(uint64_t)))
        {
            return _end;
        }

        auto convert = [&_begin](auto * val){
            std::decay_t<decltype(*val)> result;
            std::copy(_begin, _begin + sizeof(result), reinterpret_cast<std::byte *>(&result));
            _begin += sizeof(result);
            return result;
        };

        // TODO: replace with template lambda from C++ 20
        auto type = convert((TypeId *)(nullptr));
        
        switch (type)
        {
        case TypeId::Uint:
        {
            m_place.emplace<uint64_t>(convert((uint64_t *)(nullptr)));
            break;
        }
        case TypeId::Float:
        {
            m_place.emplace<double>(convert((double *)(nullptr)));
            break;
        }
        case TypeId::String:
        {
            auto stringLength = convert((uint64_t *)(nullptr));
             
            if(!hasEnoughtSpace(stringLength))
            {
                return _end;
            }
            m_place.emplace<std::string>(reinterpret_cast<const char *>(&(*_begin)), stringLength);
            _begin += stringLength;
            break;
        }
        case TypeId::Vector:
        {
            auto vectorLength = convert((uint64_t *)(nullptr));
            if(!hasEnoughtSpace(vectorLength))
            {
                return _end;
            }
            m_place.emplace<std::vector<Any>>(vectorLength);
            for(auto & any : std::get<std::vector<Any>>(m_place))
            {
                _begin = any.deserialize(_begin, _end);
            }
            break;
        }
        default:
            break;
        }
        return _begin;
    }

    TypeId getPayloadTypeId() const
    {
        return idToType[m_place.index()];
    }

    template<typename Type>
    auto& getValue() const
    {
        return std::get<Type>(m_place);
    }

    template<TypeId kId>
    auto& getValue() const
    {
        return std::get<typeToId(kId)>(m_place);
    }

    bool operator == (const Any& _o) const
    {
        return (_o.m_place == m_place);
    }

private:

    DataType m_place;
};

class IntegerType : public Any {
public:
    //TODO: replace with pretty concepts (c++20) instead of enable if
    template<typename Arg, std::enable_if_t<std::is_constructible_v<uint64_t, Arg>, bool> = false>
    explicit IntegerType(Arg && arg) : Any{std::in_place_type_t<uint64_t>{}, std::forward<Arg>(arg)}
    {
 
    }
};

class FloatType : public Any {
public:
    template<typename Arg, std::enable_if_t<std::is_constructible_v<double, Arg>, bool> = false>
    explicit FloatType(Arg && arg) : Any(std::in_place_type_t<double>{}, std::forward<Arg>(arg))
    {

    }
};

class StringType : public Any {
public:
    template<typename ...Args, std::enable_if_t<std::is_constructible_v<std::string, Args ...>, bool> = false>
    explicit StringType(Args&& ... args) : Any(std::in_place_type_t<std::string>{}, std::forward<Args...>(args...))
    {}
};

class VectorType;

template<typename T>
static constexpr bool IsValidType = std::is_same_v<IntegerType, std::decay_t<T>> ||
                                    std::is_same_v<FloatType, std::decay_t<T>> ||
                                    std::is_same_v<StringType, std::decay_t<T>> ||
                                    std::is_same_v<VectorType, std::decay_t<T>> ||
                                    std::is_same_v<Any, std::decay_t<T>>;

class VectorType : public Any {
public:
    template<typename ...Args, std::enable_if_t<(IsValidType<Args> && ...), bool> = false>
    VectorType(Args&& ...args) : Any(std::in_place_type_t<std::vector<Any>>{}, std::forward<Args...>(args...))
    {

    }

    template<typename Arg, std::enable_if_t<(IsValidType<Arg>), bool> = false>
    void push_back(Arg&& _val)
    {
        getValue<TypeId::Vector>().emplace_back(std::forward<Arg>(_val));
    }
};


class Serializator {
public:
    template<typename Arg, std::enable_if_t<(IsValidType<Arg>), bool> = false>
    void push(Arg&& _val)
    {
        m_data.emplace_back(std::forward<Arg>(_val));
    }

    Buffer serialize() const
    {
        Buffer result(sizeof(uint64_t));
        uint64_t vectorSize = m_data.size();
        std::copy(reinterpret_cast<std::byte *>(&vectorSize), reinterpret_cast<std::byte *>(&vectorSize) + sizeof(vectorSize), result.begin());
        for(auto & any : m_data)
        {
            any.serialize(result);
        }
        return result;
    }

    static std::vector<Any> deserialize(const Buffer& _val)
    {        
        if(_val.size() < sizeof(uint64_t))
        {
            return {};
        }
        uint64_t size;
        std::copy(_val.begin(), _val.begin() + sizeof(uint64_t), reinterpret_cast<std::byte *>(&size));

        if(size == 0)
        {
            return {};
        }

        auto begin = _val.begin() + sizeof(uint64_t);

        std::vector<Any> result(size);
        for(auto & any : result)
        {
            begin = any.deserialize(begin, _val.end());
        }
        return result;
    }

    const std::vector<Any>& getStorage() const
    {
        return m_data;
    }

private:
    std::vector<Any> m_data;
};


int main() {
    std::ifstream raw;
    raw.open("raw.bin", std::ios_base::in | std::ios_base::binary);
    if (!raw.is_open())
        return 1;
    raw.seekg(0, std::ios_base::end);
    std::streamsize size = raw.tellg();
    raw.seekg(0, std::ios_base::beg);

    Buffer buff(size);
    raw.read(reinterpret_cast<char*>(buff.data()), size);

    auto res = Serializator::deserialize(buff);

    Serializator s;
    for (auto&& i : res)
        s.push(i);

    std::cout << (buff == s.serialize()) << '\n';

    return 0;
}
Маньяк Робокряк колесит по городу
Re[6]: Про перемещение (на примере кода)
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 14.04.25 14:02
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Какую задачу решает, например, электрическая лампочка? Она обеспечивает освещение. А какую задачу решает мракобес, который сверлит в электрической лампочке отверстие, через которое удобно заливать керосин? Да никакую. Он просто не понимает принцип действия электрической лампочки, вот и всё.


Это ж явно террорист
Маньяк Робокряк колесит по городу
Re[8]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 14.04.25 19:07
Оценка:
Здравствуйте, Marty, Вы писали:

M>Знакомый делал тестовое задание, кидал мне посмотреть. Я у него разрешение спросил, он сказал, что можно опубликовать. Так что

M>[cut=смотри и учись]

А что хорошего в этом коде?

Мне нужно:

1. Максимально понятный код, желательно чтобы даже вчерашний студент мог подправить по мелочи.
2. Скорость — чтобы быль чем меньше лишних преобразований (особенно тяжелых — без фанатизма) и копирований памяти туда-сюда.

Т.е. пакет сетевой и скорость там все-равно не в пример ОЗУ — слишком уж фанатичные оптимизации не нужны. Но размер пакета у меня до 20 Мб, притом что выполняется на всех видах девайсах, в т.ч. на старых телефонах — где может подтормаживать (с учетом того что пакет не один).
=сначала спроси у GPT=
Отредактировано 14.04.2025 19:07 Shmj . Предыдущая версия .
Re[9]: Про перемещение (на примере кода)
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 14.04.25 19:24
Оценка:
Здравствуйте, Shmj, Вы писали:

S>А что хорошего в этом коде?


Вполне простой, вполне оптимальный, вполне расширяемый. Уровень мидла в РФ, на ЗП 3-4 кбаксов по курсу


S>Мне нужно:


S>1. Максимально понятный код, желательно чтобы даже вчерашний студент мог подправить по мелочи.


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


S>2. Скорость — чтобы быль чем меньше лишних преобразований (особенно тяжелых — без фанатизма) и копирований памяти туда-сюда.


Приведённый пример вполне отвечает этому критерию


S>Т.е. пакет сетевой и скорость там все-равно не в пример ОЗУ — слишком уж фанатичные оптимизации не нужны. Но размер пакета у меня до 20 Мб, притом что выполняется на всех видах девайсах, в т.ч. на старых телефонах — где может подтормаживать (с учетом того что пакет не один).


У тебя есть пример моего знакомого, и твои собственные экзерциссы — проверить, что быстрее работает, вроде бы не так сложно
Маньяк Робокряк колесит по городу
Re[3]: Про перемещение (на примере кода)
От: _NN_  
Дата: 14.04.25 21:08
Оценка:
Здравствуйте, Shmj, Вы писали:

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


_NN>>А можно рассказать какую всё таки задачу должен решать этот класс ?


S>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета.

Ок, непонятна связь с вопросом.
Делаем себе массив внутри и методы для работы с битами. Чем простой вариант не подходит?

Может вам Katai Struct подойдёт ?
Это язык описывающий бинарные данные и на основе него можно сгенерировать код для разных языков.
Очень удобная штука.
Из приятного в комплекте идёт Web IDE, где легко можно посмотреть как парсит, а также сетевые протоколы и различное другое.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[10]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.04.25 00:50
Оценка:
Здравствуйте, Marty, Вы писали:

M>Вполне простой, вполне оптимальный, вполне расширяемый. Уровень мидла в РФ, на ЗП 3-4 кбаксов по курсу


Но вообще если бы такая задача возникла — использовать сериализатор — то зачем его писать с нуля, когда есть готовые, годами отлаженные?

S>>2. Скорость — чтобы быль чем меньше лишних преобразований (особенно тяжелых — без фанатизма) и копирований памяти туда-сюда.

M>Приведённый пример вполне отвечает этому критерию

Ну в вашем же коде для сериализации — если нужно по итогу получить массив байт uint8_t* для FFI — нужно будет создать буффер (вектор байт) — и по сути скопировать в него все данные — расширяя _buff.resize(_buff.size() + size);

Т.е. по сути делается дурная работа — перекладывание байт из одного хранилища m_place в другое. Зафига?

Я этого шага избежал с помощью перемещения — забрал просто уже существующие байты пакета, т.к. они уже ему, готовому к умному погребению, более не понадобятся.
=сначала спроси у GPT=
Re[4]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.04.25 00:52
Оценка: :))
Здравствуйте, _NN_, Вы писали:

S>>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета.

_NN>Ок, непонятна связь с вопросом.
_NN>Делаем себе массив внутри и методы для работы с битами. Чем простой вариант не подходит?

Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно.
=сначала спроси у GPT=
Re[5]: Про перемещение (на примере кода)
От: _NN_  
Дата: 15.04.25 06:57
Оценка:
Здравствуйте, Shmj, Вы писали:

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


S>>>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета.

_NN>>Ок, непонятна связь с вопросом.
_NN>>Делаем себе массив внутри и методы для работы с битами. Чем простой вариант не подходит?

S>Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно.

Что именно вы перемещаете ?
Если это простой массив, то его перемещение это просто копирование.
Разве что память выделена в куче и тогда перемещением будет просто перемещение указателя.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[6]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.04.25 07:32
Оценка:
Здравствуйте, _NN_, Вы писали:

S>>Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно.

_NN>Что именно вы перемещаете ?
_NN>Если это простой массив, то его перемещение это просто копирование.
_NN>Разве что память выделена в куче и тогда перемещением будет просто перемещение указателя.

Зачем голый массив? Вектор для чего придумали? У него данные в куче всегда, даже если вектор создали в стеке.
=сначала спроси у GPT=
Re[7]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 15.04.25 07:45
Оценка: +1
Здравствуйте, Shmj, Вы писали:

S>Зачем голый массив? Вектор для чего придумали? У него данные в куче всегда, даже если вектор создали в стеке.


Так а что там в векторе ещё делать-то? Всё давно сделано — бери да пользуйся. Вообще не понятно, откуда взялись все те "проблемы", которые ты решаешь. У меня стойкое подозрение, что корнями всё уходит в твою безграмотность, отпять же.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 15.04.2025 7:59 rg45 . Предыдущая версия . Еще …
Отредактировано 15.04.2025 7:58 rg45 . Предыдущая версия .
Re[8]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.04.25 07:57
Оценка: :)
Здравствуйте, rg45, Вы писали:

S>>Зачем голый массив? Вектор для чего придумали? У него данные в куче всегда, даже если вектор создали в стеке.


R>Так а что там в векторе ещё делать-то? Всё давно сделано — бери да пользуйся.


Так я так и делаю

Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии.

Но в векторе, все-же, кой-чего нет — это простой возможности забрать владение на data у самого вектора.

R>Вообще не понятно, откуда взялись все те "проблемы", которые ты решаешь. У меня стойкое подозрение, что корнями всё уходит в твою безграмотность, отпять же.


А какие проблемы? Уже давно все решено и забыто — а тут пишут и пишут.
=сначала спроси у GPT=
Отредактировано 15.04.2025 7:58 Shmj . Предыдущая версия .
Re[9]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 15.04.25 08:00
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии.


Так вот в этом же и камень преткновения, как выясняется — в "вопросе наглядности". Нет никакого вопроса наглядности, есть вопрос твоей безграмотности. Всё, что тебе нужно было сделать — это инкапулировать вектор в классе и написать нормальную инициализацию объекта этого класса и потоковую сериализацию — ВСЁ! Не нужно даже определять конструкторы копирования/перемещения — с этим прекрасно справится сам компилятор. И незачем что-то там модифицировать в уже существующем пакете, пускай он в таком виде и путешествует между сокетом и прикладным уровнем.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 15.04.2025 8:07 rg45 . Предыдущая версия .
Re[9]: Про перемещение (на примере кода)
От: _NN_  
Дата: 15.04.25 08:11
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии.

Секунду, вам нужна часть вектора или весь ?
Невозможно забрать только часть без копирования.

S>Но в векторе, все-же, кой-чего нет — это простой возможности забрать владение на data у самого вектора.

Т.е. вы хотите, чтобы у вектора был интерфейс с возможностью отдать владение ?
Самым простым вариантом тогда будет использование unique_ptr<byte[]> , у него как раз есть release, который именно это и делает:

void f(char* p)
{
  delete[] p;
}

int main()
{
  std::unique_ptr<char[]> up = std::make_unique<char[]>(10);
  char* p = up.release(); // отдаём владение

  f(p);
}


Ну или свой вектор тогда можно сделать как вариант.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[9]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 15.04.25 08:28
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии.


Смотри, есть одно важное отличие С++ от С#, на которое ты до сих пор не обратил внимания. В C# все типы делятся на две группы — ссылочные типы и типы-значения. В связи с этим, способ владения объектами предопределен ещё на этапе проектирования класса или структуры. В C++ всё по-другому. Ты создаёшь класс, например, сетевого пакета, полностью абстрагируясь от того, кто и какими способами будет владеть объектами этого класса. А способы могут быть самы любые. Объект одного и того же класса может быть создан в куче, в автоматической памяти (aka стек), в статической памяти и т.п. А может всё свое время жизни существовать в виде временного объекта или даже в компайл-тайме! Поэтому в С++ вопросы владения не принято встраивать внутрь класса. А ты по инерции пытаешься сишарпные подходы применять в С++ и тебе кажется, что это "вопросы наглядности".
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 15.04.2025 9:23 rg45 . Предыдущая версия .
Re[11]: Про перемещение (на примере кода)
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 15.04.25 08:41
Оценка:
Здравствуйте, Shmj, Вы писали:


S>Но вообще если бы такая задача возникла — использовать сериализатор — то зачем его писать с нуля, когда есть готовые, годами отлаженные?


А ты зачем пишешь? Есть же готовые, годами отлаженные?


S>>>2. Скорость — чтобы быль чем меньше лишних преобразований (особенно тяжелых — без фанатизма) и копирований памяти туда-сюда.

M>>Приведённый пример вполне отвечает этому критерию

S>Ну в вашем же коде для сериализации — если нужно по итогу получить массив байт uint8_t* для FFI — нужно будет создать буффер (вектор байт) — и по сути скопировать в него все данные — расширяя _buff.resize(_buff.size() + size);


Не факт, что пакет надо сразу отправлять. Но не вижу проблем переделать под OutputIterator, который будет класть данные прямо в сокет. Но, вообще, ты не думал, что вызов системного вызова send для каждого байта по отдельности будет гораздо дороже, чем передать в send заранее подготовленный пакет целиком?

А на STM32, где я точно знаю, что никаких накладных расходов на вызов функции передачи нет — я именно там в UART и клал, через OutputIterator.


S>Т.е. по сути делается дурная работа — перекладывание байт из одного хранилища m_place в другое. Зафига?


Дурная работа — это когда ты делаешь необоснованные выводы и начинаешь заниматься оптимизацией, а потом реальность, жестокая штука, показывает, что узкое место совсем не там, и тебе приходится всё переделывать (а придётся, потому что ты сильно заложился на свои предположения)


S>Я этого шага избежал с помощью перемещения — забрал просто уже существующие байты пакета, т.к. они уже ему, готовому к умному погребению, более не понадобятся.


Это решается в несколько строчек кода через OutputIterator, который может класть хоть в сокет, хоть в массив
Маньяк Робокряк колесит по городу
Re[5]: Про перемещение (на примере кода)
От: qaz77  
Дата: 15.04.25 17:11
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно.


Не совсем понятно, как можно собрать большой вектор байтов (пакет) из нескольких маленьких векторов байт путем перемещения и без копирования.
По моему, концепция непрерывного блока памяти в векторе убивает такую идею на корню.

Ну если там части пакета это условные uint32_t, то может и норм.
Re[10]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.04.25 19:36
Оценка:
Здравствуйте, rg45, Вы писали:

R>Так вот в этом же и камень преткновения, как выясняется — в "вопросе наглядности". Нет никакого вопроса наглядности, есть вопрос твоей безграмотности. Всё, что тебе нужно было сделать — это инкапулировать вектор в классе и написать нормальную инициализацию объекта этого класса и потоковую сериализацию — ВСЁ!


У меня нет потока — отдается массив байт по FFI — т.е. дергается JS -функция и туда этот массив передается.

Я так и сделал — класс овладевает вектором, получаю нужные элементы вектора с помощью span и примитивных типов. Потом отнимаю владение, дабы не копировать, передаю в другую обертку — и уже эту обертку на выход.

R>Не нужно даже определять конструкторы копирования/перемещения — с этим прекрасно справится сам компилятор.


Конструктор и оператор копирования сразу удаляю от греха подальше, т.к. расточительно такой класс еще и копировать.

R>И незачем что-то там модифицировать в уже существующем пакете, пускай он в таком виде и путешествует между сокетом и прикладным уровнем.


Та нету тут сокета — в сокет можно потоково писать.
=сначала спроси у GPT=
Re[10]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.04.25 19:38
Оценка:
Здравствуйте, _NN_, Вы писали:

S>>Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии.

_NN>Секунду, вам нужна часть вектора или весь ?
_NN>Невозможно забрать только часть без копирования.

Части вектора в виде span — беру пока объект живой и владеет вектором — для удобства. Можно было бы все те же операции проделать до создания объекта — но это не нагладно было бы и не дешевле по ресурсам.

S>>Но в векторе, все-же, кой-чего нет — это простой возможности забрать владение на data у самого вектора.

_NN>Т.е. вы хотите, чтобы у вектора был интерфейс с возможностью отдать владение ?
_NN>Самым простым вариантом тогда будет использование unique_ptr<byte[]> , у него как раз есть release, который именно это и делает:

_NN>
_NN>void f(char* p)
_NN>{
_NN>  delete[] p;
_NN>}

_NN>int main()
_NN>{
_NN>  std::unique_ptr<char[]> up = std::make_unique<char[]>(10);
_NN>  char* p = up.release(); // отдаём владение

_NN>  f(p);
_NN>}
_NN>


_NN>Ну или свой вектор тогда можно сделать как вариант.


Ну да, вроде еще можно кастомный аллокатор для std::vector.
=сначала спроси у GPT=
Re[12]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 16.04.25 00:04
Оценка: :)
Здравствуйте, Marty, Вы писали:

M>А ты зачем пишешь? Есть же готовые, годами отлаженные?


Так у меня не было такой задачи — у меня всего лишь простой бинарный пакет. Часть заполняю в конструкторе — а часть через span и примитивные типы

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

Просто вы тут любите так написать, чтобы 4-х этажные шаблоны с FINAE, чтоб без поллитра не разобраться. А когда кто-то пишет максимально просто и понятно — считается по-лоховски.

M>Это решается в несколько строчек кода через OutputIterator, который может класть хоть в сокет, хоть в массив


Но все-равно ведь новый массив создавать? Если нет потоковой записи.
=сначала спроси у GPT=
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.