ты пишешь, что это просто для примера. А реальная задача другая, так получается. Что-то ты в показаниях путаешься.
S>А как я могу понять что фигня, если оно работает?
Это хороший вопрос
S>Возможно что было бы лучше использовать поток а не массив байт, но пока в данном случае не получается.
Эта проблема называется "мультифакторная инициализация". Вместо того, чтобы сразу создать объект в согласованном состоянии, ты создаешь неюзабельный полуфабрикат. Потом ты из этого полуфабриката пытаешься вылепить конфетку при помощи серии модификациий. Это всё уже кручено-перекручено тысячи раз.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, 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++ 20auto 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 iftemplate<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;
}
Здравствуйте, rg45, Вы писали:
R>Какую задачу решает, например, электрическая лампочка? Она обеспечивает освещение. А какую задачу решает мракобес, который сверлит в электрической лампочке отверстие, через которое удобно заливать керосин? Да никакую. Он просто не понимает принцип действия электрической лампочки, вот и всё.
Здравствуйте, Marty, Вы писали:
M>Знакомый делал тестовое задание, кидал мне посмотреть. Я у него разрешение спросил, он сказал, что можно опубликовать. Так что M>[cut=смотри и учись]
А что хорошего в этом коде?
Мне нужно:
1. Максимально понятный код, желательно чтобы даже вчерашний студент мог подправить по мелочи.
2. Скорость — чтобы быль чем меньше лишних преобразований (особенно тяжелых — без фанатизма) и копирований памяти туда-сюда.
Т.е. пакет сетевой и скорость там все-равно не в пример ОЗУ — слишком уж фанатичные оптимизации не нужны. Но размер пакета у меня до 20 Мб, притом что выполняется на всех видах девайсах, в т.ч. на старых телефонах — где может подтормаживать (с учетом того что пакет не один).
Здравствуйте, Shmj, Вы писали:
S>А что хорошего в этом коде?
Вполне простой, вполне оптимальный, вполне расширяемый. Уровень мидла в РФ, на ЗП 3-4 кбаксов по курсу
S>Мне нужно:
S>1. Максимально понятный код, желательно чтобы даже вчерашний студент мог подправить по мелочи.
Этого не было в исходных условиях. Есть ощущение, что ты не осилил сам сделать что-то годное, и поэтому появилось вот такое вот условие.
Приведённый код вполне понятный, но зависит от уровня студента. Двоечники не поймут, факт.
S>2. Скорость — чтобы быль чем меньше лишних преобразований (особенно тяжелых — без фанатизма) и копирований памяти туда-сюда.
Приведённый пример вполне отвечает этому критерию
S>Т.е. пакет сетевой и скорость там все-равно не в пример ОЗУ — слишком уж фанатичные оптимизации не нужны. Но размер пакета у меня до 20 Мб, притом что выполняется на всех видах девайсах, в т.ч. на старых телефонах — где может подтормаживать (с учетом того что пакет не один).
У тебя есть пример моего знакомого, и твои собственные экзерциссы — проверить, что быстрее работает, вроде бы не так сложно
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, _NN_, Вы писали:
_NN>>А можно рассказать какую всё таки задачу должен решать этот класс ?
S>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета.
Ок, непонятна связь с вопросом.
Делаем себе массив внутри и методы для работы с битами. Чем простой вариант не подходит?
Может вам Katai Struct подойдёт ?
Это язык описывающий бинарные данные и на основе него можно сгенерировать код для разных языков.
Очень удобная штука.
Из приятного в комплекте идёт Web IDE, где легко можно посмотреть как парсит, а также сетевые протоколы и различное другое.
Здравствуйте, Marty, Вы писали:
M>Вполне простой, вполне оптимальный, вполне расширяемый. Уровень мидла в РФ, на ЗП 3-4 кбаксов по курсу
Но вообще если бы такая задача возникла — использовать сериализатор — то зачем его писать с нуля, когда есть готовые, годами отлаженные?
S>>2. Скорость — чтобы быль чем меньше лишних преобразований (особенно тяжелых — без фанатизма) и копирований памяти туда-сюда. M>Приведённый пример вполне отвечает этому критерию
Ну в вашем же коде для сериализации — если нужно по итогу получить массив байт uint8_t* для FFI — нужно будет создать буффер (вектор байт) — и по сути скопировать в него все данные — расширяя _buff.resize(_buff.size() + size);
Т.е. по сути делается дурная работа — перекладывание байт из одного хранилища m_place в другое. Зафига?
Я этого шага избежал с помощью перемещения — забрал просто уже существующие байты пакета, т.к. они уже ему, готовому к умному погребению, более не понадобятся.
Здравствуйте, _NN_, Вы писали:
S>>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета. _NN>Ок, непонятна связь с вопросом. _NN>Делаем себе массив внутри и методы для работы с битами. Чем простой вариант не подходит?
Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, _NN_, Вы писали:
S>>>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета. _NN>>Ок, непонятна связь с вопросом. _NN>>Делаем себе массив внутри и методы для работы с битами. Чем простой вариант не подходит?
S>Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно.
Что именно вы перемещаете ?
Если это простой массив, то его перемещение это просто копирование.
Разве что память выделена в куче и тогда перемещением будет просто перемещение указателя.
Здравствуйте, _NN_, Вы писали:
S>>Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно. _NN>Что именно вы перемещаете ? _NN>Если это простой массив, то его перемещение это просто копирование. _NN>Разве что память выделена в куче и тогда перемещением будет просто перемещение указателя.
Зачем голый массив? Вектор для чего придумали? У него данные в куче всегда, даже если вектор создали в стеке.
Здравствуйте, Shmj, Вы писали:
S>Зачем голый массив? Вектор для чего придумали? У него данные в куче всегда, даже если вектор создали в стеке.
Так а что там в векторе ещё делать-то? Всё давно сделано — бери да пользуйся. Вообще не понятно, откуда взялись все те "проблемы", которые ты решаешь. У меня стойкое подозрение, что корнями всё уходит в твою безграмотность, отпять же.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
S>>Зачем голый массив? Вектор для чего придумали? У него данные в куче всегда, даже если вектор создали в стеке.
R>Так а что там в векторе ещё делать-то? Всё давно сделано — бери да пользуйся.
Так я так и делаю
Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии.
Но в векторе, все-же, кой-чего нет — это простой возможности забрать владение на data у самого вектора.
R>Вообще не понятно, откуда взялись все те "проблемы", которые ты решаешь. У меня стойкое подозрение, что корнями всё уходит в твою безграмотность, отпять же.
А какие проблемы? Уже давно все решено и забыто — а тут пишут и пишут.
Здравствуйте, Shmj, Вы писали:
S>Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии.
Так вот в этом же и камень преткновения, как выясняется — в "вопросе наглядности". Нет никакого вопроса наглядности, есть вопрос твоей безграмотности. Всё, что тебе нужно было сделать — это инкапулировать вектор в классе и написать нормальную инициализацию объекта этого класса и потоковую сериализацию — ВСЁ! Не нужно даже определять конструкторы копирования/перемещения — с этим прекрасно справится сам компилятор. И незачем что-то там модифицировать в уже существующем пакете, пускай он в таком виде и путешествует между сокетом и прикладным уровнем.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, 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);
}
Ну или свой вектор тогда можно сделать как вариант.
Здравствуйте, Shmj, Вы писали:
S>Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии.
Смотри, есть одно важное отличие С++ от С#, на которое ты до сих пор не обратил внимания. В C# все типы делятся на две группы — ссылочные типы и типы-значения. В связи с этим, способ владения объектами предопределен ещё на этапе проектирования класса или структуры. В C++ всё по-другому. Ты создаёшь класс, например, сетевого пакета, полностью абстрагируясь от того, кто и какими способами будет владеть объектами этого класса. А способы могут быть самы любые. Объект одного и того же класса может быть создан в куче, в автоматической памяти (aka стек), в статической памяти и т.п. А может всё свое время жизни существовать в виде временного объекта или даже в компайл-тайме! Поэтому в С++ вопросы владения не принято встраивать внутрь класса. А ты по инерции пытаешься сишарпные подходы применять в С++ и тебе кажется, что это "вопросы наглядности".
--
Справедливость выше закона. А человечность выше справедливости.
S>Но вообще если бы такая задача возникла — использовать сериализатор — то зачем его писать с нуля, когда есть готовые, годами отлаженные?
А ты зачем пишешь? Есть же готовые, годами отлаженные?
S>>>2. Скорость — чтобы быль чем меньше лишних преобразований (особенно тяжелых — без фанатизма) и копирований памяти туда-сюда. M>>Приведённый пример вполне отвечает этому критерию
S>Ну в вашем же коде для сериализации — если нужно по итогу получить массив байт uint8_t* для FFI — нужно будет создать буффер (вектор байт) — и по сути скопировать в него все данные — расширяя _buff.resize(_buff.size() + size);
Не факт, что пакет надо сразу отправлять. Но не вижу проблем переделать под OutputIterator, который будет класть данные прямо в сокет. Но, вообще, ты не думал, что вызов системного вызова send для каждого байта по отдельности будет гораздо дороже, чем передать в send заранее подготовленный пакет целиком?
А на STM32, где я точно знаю, что никаких накладных расходов на вызов функции передачи нет — я именно там в UART и клал, через OutputIterator.
S>Т.е. по сути делается дурная работа — перекладывание байт из одного хранилища m_place в другое. Зафига?
Дурная работа — это когда ты делаешь необоснованные выводы и начинаешь заниматься оптимизацией, а потом реальность, жестокая штука, показывает, что узкое место совсем не там, и тебе приходится всё переделывать (а придётся, потому что ты сильно заложился на свои предположения)
S>Я этого шага избежал с помощью перемещения — забрал просто уже существующие байты пакета, т.к. они уже ему, готовому к умному погребению, более не понадобятся.
Это решается в несколько строчек кода через OutputIterator, который может класть хоть в сокет, хоть в массив
Здравствуйте, Shmj, Вы писали:
S>Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно.
Не совсем понятно, как можно собрать большой вектор байтов (пакет) из нескольких маленьких векторов байт путем перемещения и без копирования.
По моему, концепция непрерывного блока памяти в векторе убивает такую идею на корню.
Ну если там части пакета это условные uint32_t, то может и норм.
Здравствуйте, rg45, Вы писали:
R>Так вот в этом же и камень преткновения, как выясняется — в "вопросе наглядности". Нет никакого вопроса наглядности, есть вопрос твоей безграмотности. Всё, что тебе нужно было сделать — это инкапулировать вектор в классе и написать нормальную инициализацию объекта этого класса и потоковую сериализацию — ВСЁ!
У меня нет потока — отдается массив байт по FFI — т.е. дергается JS -функция и туда этот массив передается.
Я так и сделал — класс овладевает вектором, получаю нужные элементы вектора с помощью span и примитивных типов. Потом отнимаю владение, дабы не копировать, передаю в другую обертку — и уже эту обертку на выход.
R>Не нужно даже определять конструкторы копирования/перемещения — с этим прекрасно справится сам компилятор.
Конструктор и оператор копирования сразу удаляю от греха подальше, т.к. расточительно такой класс еще и копировать.
R>И незачем что-то там модифицировать в уже существующем пакете, пускай он в таком виде и путешествует между сокетом и прикладным уровнем.
Та нету тут сокета — в сокет можно потоково писать.
Здравствуйте, _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.
Здравствуйте, Marty, Вы писали:
M>А ты зачем пишешь? Есть же готовые, годами отлаженные?
Так у меня не было такой задачи — у меня всего лишь простой бинарный пакет. Часть заполняю в конструкторе — а часть через span и примитивные типы
Я вообще не понимаю что не так. Память лишний раз не выделяется, очень наглядно.
Просто вы тут любите так написать, чтобы 4-х этажные шаблоны с FINAE, чтоб без поллитра не разобраться. А когда кто-то пишет максимально просто и понятно — считается по-лоховски.
M>Это решается в несколько строчек кода через OutputIterator, который может класть хоть в сокет, хоть в массив
Но все-равно ведь новый массив создавать? Если нет потоковой записи.