Мне нужно забрать владение над TrackedClass у Wrapper.
И вот в чем прикол. В онлайн-компиляторе вывод:
Constructor id=1
Destructor id=1
desctructed=false
test
А у меня в XCode тот же самый код:
Constructor id=1
Destructor id=1
desctructed=true
test
Т.е. если так попытаться заиметь владение — можно получить по башке, получается?
Получается в онлайн этом компилере — деструктор то вызывается, но видимо он сбрасывает значение поля? Т.е. в любом случае такое перемещение — не позволяет сохранить объект и передать.
Добавил:
TrackedClass&& fun1() {
Wrapper w = Wrapper();
return std::move(w.take());
}
S>Т.е. если можно написать проще — то нужно писать проще, без доп. оберток.
Если основной критерий выбора языка это простота, то как ты вообще оказался на этом форуме?
Я думал тебе нужен перформанс и максимальная кроссплатформенность.
S>Сетевой бинарный пакет. С помощью методов устанавливаем удобным образом отдельные части пакета — как то версия, тип пакета и пр. Но так же должна быть возможность получить данные пакета целиком для отправки (или предварительного шифрования).
У меня знакомый делал подобную задачу. Задаётся type_id константой, задаётся низлежащий тип, так же может быть вектор подобных значений. Он её делал как тестовое. У него всё вычислялось в компайл-тайме, и вообще, код бы на порядок, не, на два, лучше. За день вроде сделал.
Здравствуйте, Marty, Вы писали:
M>Ну, если подсунуть компайл-тайм пакет, то вроде всё в компайле и отработает. Рантайм пакет конечно какая-то часть будет в рантайме работать
Все в рантайме, конечно.
Компил-тайм этот редко где вообще нужен — в основном идет как ребус или хорохориться друг перед другом на форумах. В серьезных проектах он не используется или используется по минимуму — совсем простые вещи.
Здравствуйте, Shmj, Вы писали:
_NN>>А можно рассказать какую всё таки задачу должен решать этот класс ?
S>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета.
Здравствуйте, _NN_, Вы писали:
S>>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета. _NN>Ок, непонятна связь с вопросом. _NN>Делаем себе массив внутри и методы для работы с битами. Чем простой вариант не подходит?
Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно.
Здравствуйте, rg45, Вы писали:
R>Во-вторых, а нафига было отдавать этот вектор какому-то объекту класса, если потом всё равно забирать?
Только лишь по одной причине — мы люди и нам удобнее код разбивать на функции, удобнее манипулировать объектами — так легче управлять.
Если бы не наша человечность и свойственные нашей психике паттерны восприятия — то не нужно было бы разбивать код на множество функций и создавать объекты — все бы писалось одним большим куском в машинных кодах.
R>Смотри, что получается, на входе у тебя какие-то входные данные для создания пакета, на выходе — вектор байт. То есть, это простая функция без побочных эффектов с чётким входом и выходом.
Это можно было бы оформить в виде функции — но так не удобно. Т.е. типа передаем вектор в функцию, эта функция последовательно устанавливает части вектора, основываясь на неких индексах. Но так не наглядно — все индексы перенесены в класс-обертку а функция просто получает части этого пакета у удобном виде по названию, не думая об индексах и смещениях.
R>На кой хер, спрашивается, ты превращаешь это в машину состояний? Потому что в сишарп всё на классах?
Тут нет машины состояний — по прежнему просто доступ к частям массива, просто удобным образом.
R>То же самое и про "другую обертку". Ты не замечаешь, что у тебя обёртки ради обёрток?
А как же. Это для удобства восприятия. Как и разбиение на функции — можно было бы не разбивать, все писать в одной большой функции — зачем плодить, если можно в одной?
R>У тебя же вектор — это не какая-то там деталь реализации, которую нужно инкапсулировать — вектор у тебя находится прямо в модели данных самого верхнего уровня. На кой хер ты его пхнёшь в какие-то промежуточные обёртки, то "овладевая", то "отбирая"? Что за свингер-пати ты устроил?
Чтобы было наглядно и удобно — не работать с индексами а лишь получать удобно читаемые названия.
Здесь сначала происходит перемещение содержимого объекта _trackedClass в локальный объект tc. И нужно понимать, что время жизни объекта _trackedClass при этом не заканчивается. Скорлупа этого (под)объекта будет жить, пока живет его полный объект. Также примечательно то, что объект tc является move eligible. Это означает, что, если к нему не будет применена NRVO, то к нему будет применено ещё одно перемещение. Т.е. либо NRVO (что скорее всего), либо второе перемещение, копирования точно не будет.
Как бы то ни было, хотя бы одного перемещения здесь не избежать. Поэтому смысла в этом локальном объекте tc нет никакого. Лучше писать просто:
Так и код проще, и потенциальное число перемещений меньше.
S>
S>TrackedClass fun1() {
S> Wrapper w = Wrapper();
S> TrackedClass t = w.take(); // <- копия НЕ порождается.
S> return t;
S>}
S>
Строго говоря, здесь копия может порождаться, а может нет. Стандарт языка не регламентирует число промежуточных копий, это implementation specifics. Но, если NRVO таки было подключено, что скорее всего, тогда никаких дополнительных копий не будет.
К счастью, этот пример можно легко видоизменить так, чтоб вместо необязательной NRVO применялась обязательная RVO:
TrackedClass fun1() {
Wrapper w = Wrapper();
return w.take(); // <- копия НЕ порождается СТОПУДОВО!
}
То, что в этом случает не будет промежуточных копий, гарантируется стандартом языка.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Shmj, Вы писали:
S>Т.е. если так попытаться заиметь владение — можно получить по башке, получается?
Не можно, а нужно. Время жизни объекта закончилось при выходе из функции. И ссылка, которую ты вернул, оказывается невалидной, прямо сразу же. Всё дальнейшее поведение программы — это UB.
Сколько лет ты уже толчёшься на этом форуме, но не потрудился освоить даже азы. Какие слова тебе нужно сказать, чтоб до тебя дошло. что C++ — не C#?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Shmj, Вы писали:
S>Так дело вот в чем. Я с 15 декабря примерно без выходных и праздников работаю с другим языком (не прикасался к C++). Мозг работает на 30%.
Смотри какая штука. В С++ никогда нельзя выпускать из виду время жизни объектов. Нет и быть не может универсального ответа на вопрос, как правильно возвращать — по ссылке или по значению. Всё определяется решаемой задачей и семантикой того или иного класса и той или иной функции. Это же относится и к твоему классу враппера. Его можно было бы реализовать и так, и эдак, вероятно, какое-то решение оказалось бы более выгодным. Чтобы выбрать оптимальный вариант дизайна, нужно знать больше деталей, которые не видны в твоём синтетическом примере. Но вот при реализации функции fun1, программист однозначно прошёлкал этот момент. Каким бы ни был дизай класса враппера, как бы он ни возвращал результат, по ссылке или по значению, функция fun1 реализована некорректно и возвращает битую ссылку. И чтобы исправить ошибку, нужно просто сделать, чтоб fun1 возвращала по значению.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Каким бы ни был дизай класса враппера, как бы он ни возвращал результат, по ссылке или по значению, функция fun1 реализована некорректно и возвращает битую ссылку. И чтобы исправить ошибку, нужно просто сделать, чтоб fun1 возвращала по значению.
S>>Вот это, получается, канонический вариант. Но не красивый — добавляет в код угловатости.
M>Конкретно что не устраивает? M>В с++ за владение объектом и его передачу как раз таки отвечает специально для этого придуманный unique_ptr.
Ну вроде и без unique_ptr все работает за счет RVO. Наш авторитет сказал выше что гарантируется стандартом, можно опираться на это. Т.е. если можно написать проще — то нужно писать проще, без доп. оберток.
Здравствуйте, Shmj, Вы писали:
M>>Конкретно что не устраивает? M>>В с++ за владение объектом и его передачу как раз таки отвечает специально для этого придуманный unique_ptr.
S>Ну вроде и без unique_ptr все работает за счет RVO. Наш авторитет сказал выше что гарантируется стандартом, можно опираться на это. Т.е. если можно написать проще — то нужно писать проще, без доп. оберток.
На самом деле, вариант с умным указателем ближе к привычным тебе ссылочным типам. С shared_ptr было бы ещё ближе. С двумя лишь отличиями: детерминированность времени жизни (это плюс) и неспособность разруливать циклические зависимости (это минус, который частично можно компенсировать использованием weak_ptr).
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, __kot2, Вы писали:
__>Установка данных частями это уже антипаттерн, так как можем легко поломать их консистентность. Он должен быть разбит на атомарные независимые вещи
Всегда все устанавливают данные пакета частями одну часть за другой
А какие альтернативы?
__>я никогда не работал с пакетами напрямую, но это выглядит примерно так — есть данные для отправки, есть служебные структуры пакета, это все сериализуется куда-то, это все разные классы. встает вопрос о переиспользовании буфера для отправки другого пакета. так вот не надо это делать так явно. мы сериализуем данные и отдаем их куда-то для отправки, берем новый буфер и туда уже пихаем новые данные. а вот аллокатор для этих буферов может иметь некий кольцевой буфер в выделенном заранее массивчике или около того, чтобы там не new/delete работал медленно, а что-то кастомное
Единый буффер — может и не плохо, но если многопоточная среда — то усложняет значительно. Нужно по количеству потоков выделять n буфферов этих, ожидание, очередь. Возможно добавлю, если будет время, но для начала чтобы хотя бы лишние копии на пустом месте не создавались.
Во-первых, говнокод. На кой хер у тебя в классе пакета такое количество модифицирующих аксессоров. Тебе действительно нужно многократно менять свойсва объекта пакета? Нафига? Сисшарп головного мозга какой-то.
Во-вторых, это ты скажи, что не так. И нафига тебе заливать керосин в электрическую лампочку.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, 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>Какую задачу решает, например, электрическая лампочка? Она обеспечивает освещение. А какую задачу решает мракобес, который сверлит в электрической лампочке отверстие, через которое удобно заливать керосин? Да никакую. Он просто не понимает принцип действия электрической лампочки, вот и всё.
Здравствуйте, Shmj, Вы писали:
S>Зачем голый массив? Вектор для чего придумали? У него данные в куче всегда, даже если вектор создали в стеке.
Так а что там в векторе ещё делать-то? Всё давно сделано — бери да пользуйся. Вообще не понятно, откуда взялись все те "проблемы", которые ты решаешь. У меня стойкое подозрение, что корнями всё уходит в твою безграмотность, отпять же.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
S>>Зачем голый массив? Вектор для чего придумали? У него данные в куче всегда, даже если вектор создали в стеке.
R>Так а что там в векторе ещё делать-то? Всё давно сделано — бери да пользуйся.
Так я так и делаю
Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии.
Но в векторе, все-же, кой-чего нет — это простой возможности забрать владение на data у самого вектора.
R>Вообще не понятно, откуда взялись все те "проблемы", которые ты решаешь. У меня стойкое подозрение, что корнями всё уходит в твою безграмотность, отпять же.
А какие проблемы? Уже давно все решено и забыто — а тут пишут и пишут.
Здравствуйте, Marty, Вы писали:
M>А ты зачем пишешь? Есть же готовые, годами отлаженные?
Так у меня не было такой задачи — у меня всего лишь простой бинарный пакет. Часть заполняю в конструкторе — а часть через span и примитивные типы
Я вообще не понимаю что не так. Память лишний раз не выделяется, очень наглядно.
Просто вы тут любите так написать, чтобы 4-х этажные шаблоны с FINAE, чтоб без поллитра не разобраться. А когда кто-то пишет максимально просто и понятно — считается по-лоховски.
M>Это решается в несколько строчек кода через OutputIterator, который может класть хоть в сокет, хоть в массив
Но все-равно ведь новый массив создавать? Если нет потоковой записи.
Здравствуйте, _NN_, Вы писали:
S>>Части вектора в виде span — беру пока объект живой и владеет вектором — для удобства. Можно было бы все те же операции проделать до создания объекта — но это не нагладно было бы и не дешевле по ресурсам. _NN>Всё хорошо, возвращаем span и не копируем лишний раз. _NN>Только это не связано с тем, что в C++ называется перемещением.
Перемещение уже производим в самом конце — когда пакет сформирован. Берем у этого класса-пакета байты вектора и конфискуем — передаем далее (в другую обертку).
Здравствуйте, Shmj, Вы писали:
S>Перемещение уже производим в самом конце — когда пакет сформирован. Берем у этого класса-пакета байты вектора и конфискуем — передаем далее (в другую обертку).
Во-первых, не у класса, а у объекта класса. Во всём и везеде у тебя нечёткость и, как следствие, сплошная путаница. Срала-мазала.
Во-вторых, а нафига было отдавать этот вектор какому-то объекту класса, если потом всё равно забирать? Смотри, что получается, на входе у тебя какие-то входные данные для создания пакета, на выходе — вектор байт. То есть, это простая функция без побочных эффектов с чётким входом и выходом. На кой хер, спрашивается, ты превращаешь это в машину состояний? Потому что в сишарп всё на классах?
То же самое и про "другую обертку". Ты не замечаешь, что у тебя обёртки ради обёрток? У тебя же вектор — это не какая-то там деталь реализации, которую нужно инкапсулировать — вектор у тебя находится прямо в модели данных самого верхнего уровня. На кой хер ты его пхнёшь в какие-то промежуточные обёртки, то "овладевая", то "отбирая"? Что за свингер-пати ты устроил?
Завязывал бы ты со своей трепологией и занялся бы делом, наконец.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, _NN_, Вы писали:
_NN>В таком варианте неясно зачем нужно вытаскивать вектор, и не работать просто с объектом Packet.
Это не первый его пост в таком духе. Он никогда не рассматривает вариант, что в его решении что-то может быть не совсем идеально, поэтом сразу переходит к обсуждению глобальных проблем С++ на абстрактных примерах.
--
Справедливость выше закона. А человечность выше справедливости.
S>Перемещение уже производим в самом конце — когда пакет сформирован. Берем у этого класса-пакета байты вектора и конфискуем — передаем далее (в другую обертку).
Если ты возвращаешь/передаешь вектор — там, скорее всего тоже будет перемещение, компилятор всё за тебя сделает. Ты упёрся в один не самый главный аспект, а в остальном наколбасил кучу говна
— работаем с _packetBytes. А вот _storage может быть как внешним (тогда в классе он не установлен), так и внутренним — в зависимости генерим пакет или парсим.
Но вы зрите в корень, возможно еще вернусь к этому.
Вот это очень наивная попытка обнаружить окончание времени жизни объекта. Если объект уже разрушен, то вызов нестатической функции-члена для этого объекта порождает неопределённое поведение. Вместо статуса объекта программа может вывести тебе расписание поездов на Луну. А может отформатировать винчестер.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, T4r4sB, Вы писали:
TB>Вот тут тоже убери двойной амперсанд. В переменной значение же? Значит так и пиши в объявлении
Ну смотрите, получается что обычный возврат return из функции — может порождать копию. Так ведь?
class Wrapper {
private:
TrackedClass _trackedClass;
public:
Wrapper() : _trackedClass(1) {
}
TrackedClass take() {
return _trackedClass; // <- тут копия порождается.
}
};
TrackedClass fun1() {
Wrapper w = Wrapper();
TrackedClass t = w.take();
return t;
}
Однако если срабатывает некая оптимизация, то копия уже не порождается. К примеру тут:
class Wrapper {
private:
TrackedClass _trackedClass;
public:
Wrapper() : _trackedClass(1) {
}
TrackedClass take() {
TrackedClass tc = std::move(_trackedClass); // <- Копия не порождается.return tc;
}
};
TrackedClass fun1() {
Wrapper w = Wrapper();
TrackedClass t = w.take(); // <- копия НЕ порождается.return t;
}
int main(int argc, const char * argv[]) {
TrackedClass t = fun1();
t.test();
std::cout << "test" << std::endl;
}
Правильно ли я понял, что это благодаря умному компилятору и NRVO ? И всегда ли можно на это рассчитывать, всегда ли имеем 100% гарантию что копия не порождается?
Здравствуйте, rg45, Вы писали:
R>Сколько лет ты уже толчёшься на этом форуме, но не потрудился освоить даже азы. Какие слова тебе нужно сказать, чтоб до тебя дошло. что C++ — не C#?
Так дело вот в чем. Я с 15 декабря примерно без выходных и праздников работаю с другим языком (не прикасался к C++). Мозг работает на 30%.
И тут срочно понадобилось внести правки в старый C++ проект.
И вроде бы разбирался с этим всем — но сейчас другой язык с умным сборщиком мусора очистил и облегчил мой мозг — и приходится как бы заново разбираться.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, T4r4sB, Вы писали:
TB>>Вот тут тоже убери двойной амперсанд. В переменной значение же? Значит так и пиши в объявлении
S>Ну смотрите, получается что обычный возврат return из функции — может порождать копию. Так ведь?
S>Правильно ли я понял, что это благодаря умному компилятору и NRVO ? И всегда ли можно на это рассчитывать, всегда ли имеем 100% гарантию что копия не порождается?
Есть правила когда оптимизация точно сработает.
Есть случаи когда она точно НЕ сработает, и как минимум надо понимать как эта оптимизация устроена технически. Например при возврате поля компилятор не знает нужно ли тебе это поле в дальнейшем, так что если нет то руками ставь std move
И есть случаи когда зависит от компилятора но их сравнительно мало.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, rg45, Вы писали:
R>Как бы то ни было, хотя бы одного перемещения здесь не избежать. Поэтому смысла в этом локальном объекте tc нет никакого.
А раз уж хотя бы одного перемещения не избежать ни при каких раскладах, твоя функция-член take може спокойненько возвращать rvalue ссылку. И перемешение в этом случае будет происходить не в take, а в fun1:
TrackedClass&& take() {
return std::move(_trackedClass); // Ни копии, ни перемещения - просто возврат rvalue ссылки.
}
TrackedClass fun1() {
Wrapper w = Wrapper();
return w.take(); // То самое одно неизбежное перемещение.
}
Но признаться, меня несколько напрягает такой дизайн. Вот нафига вот это безусловное встраивание move внутрь каждого неконстантного объекта? Не лучше ли просто обеспечить оптимальный доступ к члену класса в зависимости от категории выражения, а вызвающий метод уже пускай сам делает move, если захочет:
Здравствуйте, Marty, Вы писали:
S>>Во втором примере — все работает как нужно, копирования не происходит. Но есть ли гарантия что это будет работать везде? M>Ну что за чушь? Если UB у тебя случайно работает, как ты ожидаешь, то тебе просто очень повезло. А могло бы и винт отформатировать
А где вы там увидели UB, тем более уровня критического. Максимум что там может произойти — излишнее копирование.
Здравствуйте, rg45, Вы писали:
R>На самом деле, вариант с умным указателем ближе к привычным тебе ссылочным типам. С shared_ptr было бы ещё ближе. С двумя лишь отличиями: детерминированность времени жизни (это плюс) и неспособность разруливать циклические зависимости (это минус, который частично можно компенсировать использованием weak_ptr).
Т.е. оно в принципе можно и std::option<std::reference_wrapper<Obj1>> писать и получить фактически те же удобные ссылки без доп. нагрузки на управление памятью. Или умные указатели — так же фактически все делает за тебя.
Но! Добавляется невыразительность и угловатость кода на пустом месте.
S>Т.е. оно в принципе можно и std::option<std::reference_wrapper<Obj1>> писать и получить фактически те же удобные ссылки без доп. нагрузки на управление памятью. Или умные указатели — так же фактически все делает за тебя.
S>Но! Добавляется невыразительность и угловатость кода на пустом месте.
А, вот ты о чём. Тоже мне, проблема. Алиасы же есть:
S>Т.е. оно в принципе можно и std::option<std::reference_wrapper<Obj1>> писать и получить фактически те же удобные ссылки без доп. нагрузки на управление памятью
Угу, а можно на потолке спать. Ты, походу, вообще предмет не просекаешь, раз пишешь эту дичь.
Если хочешь, чтоб я тебе пояснил, чего именно ты не понимаешь, перепиши вот этот свой фрагмент с использованием std::option<std::reference_wrapper<Obj1>> и я подробно тебе всё разжую. А может, дойдешь самостоятельно.
Здравствуйте, Shmj, Вы писали:
S>Все-равно не наглядно и не удобно — держать в памяти все эти алиасы. Если бы он один был на проект то ОК. Но их же будет плодиться как мух.
Просто не нужно делать область видимости имен больше, чем это реально нужно, и всё будет норм. И это относится не только к алиасам, а вообще ко всем именам.
--
Справедливость выше закона. А человечность выше справедливости.
Конструкторы копирования/перемещения здесь ни разу не понадобились даже формально.
Ты можешь даже явно удалить их, и убедиться в этом. Фактически, ты просто разжился rvalue-ссылкой, но нигде ею не воспользовался для перемещений.
Если бы передавал по значению, то в некоторых местах мог бы словить оптимизацию copy elision. То есть, компилятору формально нужны конструкторы, но он ими не воспользуется.
Не надо бояться промежуточных копирований, а тем более, промежуточных перемещений.
Если объект сам по себе не слишком большого размера, то даже если компилятор не оптимизирует размещение его промежуточных копий, то правильно написанный (а в ряде случаев — реализованный по умолчанию) конструктор перемещения не отнимет много процессорного времени.
Всякие там std::vector прекрасно перемещаются сами — передавая своё содержимое от одного экземпляра другому.
Объекты надо возвращать (и желательно, принимать тоже) по значению, а не по rvalue-ссылке.
Так ты с большей гарантией не получишь висячую ссылку, как в коде примера.
Соответственно, дизайн кода у обёртки — например, такой
Здесь мы говорим, что ссылка на поле — временная, поэтому конструктор возвращаемого значения — это конструктор перемещения. То есть, можно украсть контент.
Если же объект реально тяжеловесный сам по себе, и не хочется морочить голову вопросами его перемещения — то умные указатели тебе в помощь. Заодно и работу со стеком разгрузишь.
std::unique_ptr не имеет конструктора копирования, так что заодно получишь диагностику от компилятора обо всех местах, где ты явно или неявно пытался копировать вместо перемещения. Ну и там, где нужно именно копирование, — сделаешь его руками.
Здравствуйте, Кодт, Вы писали:
К>Если же объект реально тяжеловесный сам по себе, и не хочется морочить голову вопросами его перемещения — то умные указатели тебе в помощь. Заодно и работу со стеком разгрузишь.
Так std:vector же хранит свои большие данные (сам массив) — не в стеке а в куче, даже если сам vector создан в стеке. И если RVO работает — то зачем лишние обертки?
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, Кодт, Вы писали:
К>>Если же объект реально тяжеловесный сам по себе, и не хочется морочить голову вопросами его перемещения — то умные указатели тебе в помощь. Заодно и работу со стеком разгрузишь.
S>Так std:vector же хранит свои большие данные (сам массив) — не в стеке а в куче, даже если сам vector создан в стеке. И если RVO работает — то зачем лишние обертки?
У тебя вообще в плане дизайна весь код какой-то кривой. Всякие там getdata это как правило хреновый дизайн. Всегда можно без этого сделать компактнее и яснее
Здравствуйте, Shmj, Вы писали:
К>>Если же объект реально тяжеловесный сам по себе, и не хочется морочить голову вопросами его перемещения — то умные указатели тебе в помощь. Заодно и работу со стеком разгрузишь. S>Так std:vector же хранит свои большие данные (сам массив) — не в стеке а в куче, даже если сам vector создан в стеке. И если RVO работает — то зачем лишние обертки?
Так вот вектор не тяжеловесный сам по себе. Что там внутри, один указатель и два размера.
Поэтому там конструктор перемещения сводится к трём операциям обмена.
Но если у тебя какая-нибудь мега-структура с множеством полей, которые нужно как-то копировать-переносить, — то моли бога, чтобы её можно было оптимизировать до memcpy.
А уж если эти поля с нетривиальными конструкторами...
По сути, использовать unique_ptr (или shared_ptr, возможно, в тандеме с CopyOnWrite) — это такие разновидности идиомы PIMPL. Лёгкий фасад и тяжёлая реализация (в том числе, реализация копирования).
Здравствуйте, __kot2, Вы писали:
S>>Так std:vector же хранит свои большие данные (сам массив) — не в стеке а в куче, даже если сам vector создан в стеке. И если RVO работает — то зачем лишние обертки? __>У тебя вообще в плане дизайна весь код какой-то кривой. Всякие там getdata это как правило хреновый дизайн. Всегда можно без этого сделать компактнее и яснее
Здравствуйте, Кодт, Вы писали:
К>Так вот вектор не тяжеловесный сам по себе. Что там внутри, один указатель и два размера. К>Поэтому там конструктор перемещения сводится к трём операциям обмена.
Но это перемещения. А ведь копирование может быть весьма затратным, там же и гигабаты могут быть.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, __kot2, Вы писали:
S>>>Так std:vector же хранит свои большие данные (сам массив) — не в стеке а в куче, даже если сам vector создан в стеке. И если RVO работает — то зачем лишние обертки? __>>У тебя вообще в плане дизайна весь код какой-то кривой. Всякие там getdata это как правило хреновый дизайн. Всегда можно без этого сделать компактнее и яснее
S>На пример как?
Для этого нужно целиком задачу понимать. Сформулируй ее как нить более цельно
Здравствуйте, __kot2, Вы писали:
S>>На пример как? __>Для этого нужно целиком задачу понимать. Сформулируй ее как нить более цельно
Сетевой бинарный пакет. С помощью методов устанавливаем удобным образом отдельные части пакета — как то версия, тип пакета и пр. Но так же должна быть возможность получить данные пакета целиком для отправки (или предварительного шифрования).
Здравствуйте, Marty, Вы писали:
M>У меня знакомый делал подобную задачу. Задаётся type_id константой, задаётся низлежащий тип, так же может быть вектор подобных значений. Он её делал как тестовое. У него всё вычислялось в компайл-тайме, и вообще, код бы на порядок, не, на два, лучше. За день вроде сделал.
В компил-тайме не получится, т.к. тип пакета узнаю на основе поступающих данных.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, __kot2, Вы писали:
S>>>На пример как? __>>Для этого нужно целиком задачу понимать. Сформулируй ее как нить более цельно
S>Сетевой бинарный пакет. С помощью методов устанавливаем удобным образом отдельные части пакета — как то версия, тип пакета и пр. Но так же должна быть возможность получить данные пакета целиком для отправки (или предварительного шифрования).
Установка данных частями это уже антипаттерн, так как можем легко поломать их консистентность. Он должен быть разбит на атомарные независимые вещи
я никогда не работал с пакетами напрямую, но это выглядит примерно так — есть данные для отправки, есть служебные структуры пакета, это все сериализуется куда-то, это все разные классы. встает вопрос о переиспользовании буфера для отправки другого пакета. так вот не надо это делать так явно. мы сериализуем данные и отдаем их куда-то для отправки, берем новый буфер и туда уже пихаем новые данные. а вот аллокатор для этих буферов может иметь некий кольцевой буфер в выделенном заранее массивчике или около того, чтобы там не new/delete работал медленно, а что-то кастомное
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, __kot2, Вы писали:
__>>Установка данных частями это уже антипаттерн, так как можем легко поломать их консистентность. Он должен быть разбит на атомарные независимые вещи
S>Всегда все устанавливают данные пакета частями одну часть за другой
ну я и говорю, у меня опыта в этой области нет, исхожу просто из общих соображений
S>Единый буффер — может и не плохо, но если многопоточная среда — то усложняет значительно. Нужно по количеству потоков выделять n буфферов этих, ожидание, очередь. Возможно добавлю, если будет время, но для начала чтобы хотя бы лишние копии на пустом месте не создавались.
один из решений это возвращать что-то в духе buffer_view по аналогии с string_view, то есть невладеющий указатель
Здравствуйте, Shmj, Вы писали:
S>Компил-тайм этот редко где вообще нужен — в основном идет как ребус или хорохориться друг перед другом на форумах. В серьезных проектах он не используется или используется по минимуму — совсем простые вещи.
По-моему, ты злоупотребляешь обобщениями.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Shmj, Вы писали:
S>Единый буффер — может и не плохо, но если многопоточная среда — то усложняет значительно. Нужно по количеству потоков выделять n буфферов этих, ожидание, очередь. Возможно добавлю, если будет время, но для начала чтобы хотя бы лишние копии на пустом месте не создавались.
Если ты не террабиты собрался прокачивать, то существующие в плюсах оптимизации вполне себе справятся, а ты занимаешься херней
Здравствуйте, Shmj, Вы писали:
S>>>На пример как? __>>Для этого нужно целиком задачу понимать. Сформулируй ее как нить более цельно
S>Сетевой бинарный пакет. С помощью методов устанавливаем удобным образом отдельные части пакета — как то версия, тип пакета и пр. Но так же должна быть возможность получить данные пакета целиком для отправки (или предварительного шифрования).
Не надо так делать: поменяется версия и всё по новой переписывать придётся.
Данные отдельно, отсылка данных отдельно.
Цепочка примерно такая: прикладные данные -> спец struct данных для сообщения -> серилизация в буфер (в зависимости от версии) -> отсылка буфера (и только если данные по настоящему большие (что для бинарных протоколов не характерно, то серилизация прямо в поток вывода).
В любом случае под каждое сообщение должна быть структура данных, иначе это write only код, замучаетесь поддерживать.
Здравствуйте, Shmj, Вы писали:
__>>Установка данных частями это уже антипаттерн, так как можем легко поломать их консистентность. Он должен быть разбит на атомарные независимые вещи S>Всегда все устанавливают данные пакета частями одну часть за другой
Здравствуйте, _NN_, Вы писали:
_NN>А можно рассказать какую всё таки задачу должен решать этот класс ?
Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета.
Здравствуйте, Marty, Вы писали:
S>>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета. M>Не решает. Не удобно. Не легким способом.
А что вам удобно? Чем плохо обернуть массив байт пакета и получать/устанавливать те или иные части в удобном виде просто как вызов функции get|set?
Здравствуйте, Shmj, Вы писали:
S>А что вам удобно? Чем плохо обернуть массив байт пакета и получать/устанавливать те или иные части в удобном виде просто как вызов функции get|set?
Тепреть не могу аналогий, но в данном случае будет уместно, всё-таки.
Какую задачу решает, например, электрическая лампочка? Она обеспечивает освещение. А какую задачу решает мракобес, который сверлит в электрической лампочке отверстие, через которое удобно заливать керосин? Да никакую. Он просто не понимает принцип действия электрической лампочки, вот и всё.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
S>>А что вам удобно? Чем плохо обернуть массив байт пакета и получать/устанавливать те или иные части в удобном виде просто как вызов функции get|set? R>Тепреть не могу аналогий, но в данном случае будет уместно, всё-таки.
S>>- что не так и какие альтернативы?
R>Во-первых, говнокод. На кой хер у тебя в классе пакета такое количество модифицирующих аксессоров. Тебе действительно нужно многократно менять свойсва объекта пакета? Нафига? Сисшарп головного мозга какой-то.
Ну это ж для примера. На самом деле из сеттеров setAuthCode в одном пакете типа uint64_t. Не устанавливаю сразу, т.к. удобнее внутри класса сформировать нужные поля для аутентификации.
Но геттеров больше, т.к. пакет работает в обе стороны — и формируем и парсим. И все в одном месте как бы.
R>Во-вторых, это ты скажи, что не так. И нафига тебе заливать керосин в электрическую лампочку.
Мне нужно сформировать массив байт с определенной структурой. В поток писать нельзя, т.к. этот пакет передается через FFI и там нет возможности захватить поток и в него писать.
: какую задачу ты решаешь. На этот вопрос ты так и не ответил. Превращение C++ в C# — это не задача — это заливание керосина в электрическую лампочку.
Я ж писал — бинарный пакет с удобным доступом к частям пакета. В обе стороны — и формируем и парсим.
R>P.S. Задача могла бы звучать, например, так: "помогите спроектировать класс пакета, а то у меня какая-то фигня получается".
А как я могу понять что фигня, если оно работает?
Возможно что было бы лучше использовать поток а не массив байт, но пока в данном случае не получается.
Здравствуйте, Shmj, Вы писали:
S>Ну это ж для примера. На самом деле из сеттеров setAuthCode в одном пакете типа uint64_t. Не устанавливаю сразу, т.к. удобнее внутри класса сформировать нужные поля для аутентификации.
Я понял, что это для примера. Вопрос только, для какого примера. Какую задачу ты решаешь? Да никакую — так получается. Борешься с неприятным С++, чтобы сделать его приятным, как С#.
--
Справедливость выше закона. А человечность выше справедливости.
ты пишешь, что это просто для примера. А реальная задача другая, так получается. Что-то ты в показаниях путаешься.
S>А как я могу понять что фигня, если оно работает?
Это хороший вопрос
S>Возможно что было бы лучше использовать поток а не массив байт, но пока в данном случае не получается.
Эта проблема называется "мультифакторная инициализация". Вместо того, чтобы сразу создать объект в согласованном состоянии, ты создаешь неюзабельный полуфабрикат. Потом ты из этого полуфабриката пытаешься вылепить конфетку при помощи серии модификациий. Это всё уже кручено-перекручено тысячи раз.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, 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 в другое. Зафига?
Я этого шага избежал с помощью перемещения — забрал просто уже существующие байты пакета, т.к. они уже ему, готовому к умному погребению, более не понадобятся.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, _NN_, Вы писали:
S>>>Уже писал выше — быть удобным способом работать с бинарным сетевым пакетом — позволять легким способом устанавливать/получать те или иные части пакета. _NN>>Ок, непонятна связь с вопросом. _NN>>Делаем себе массив внутри и методы для работы с битами. Чем простой вариант не подходит?
S>Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно.
Что именно вы перемещаете ?
Если это простой массив, то его перемещение это просто копирование.
Разве что память выделена в куче и тогда перемещением будет просто перемещение указателя.
Здравствуйте, _NN_, Вы писали:
S>>Так я так и сделал, все работает. По завершению формирования пакета вместо копирования байт — просто завладеваю ими (перемещаю) — отнимаю владение у пакета. Но тут народ говорит что это не умно. _NN>Что именно вы перемещаете ? _NN>Если это простой массив, то его перемещение это просто копирование. _NN>Разве что память выделена в куче и тогда перемещением будет просто перемещение указателя.
Зачем голый массив? Вектор для чего придумали? У него данные в куче всегда, даже если вектор создали в стеке.
Здравствуйте, 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.
Здравствуйте, qaz77, Вы писали:
Q>Не совсем понятно, как можно собрать большой вектор байтов (пакет) из нескольких маленьких векторов байт путем перемещения и без копирования. Q>По моему, концепция непрерывного блока памяти в векторе убивает такую идею на корню.
У меня изначально создается нужного размера вектор в конструкторе, частично заполняю данными. Но для удобства часть данных вносятся позже через сеттеры. Там где нужно сделать преобразование бинарных данных — доступ через span без выделения памяти.
Q>Ну если там части пакета это условные uint32_t, то может и норм.
Часть мелких — типа timestamp или noise. Но так же и доступ через span без выделения памяти.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, _NN_, Вы писали:
S>>>Но еще есть вопрос наглядности — а это тоже важно. Чтобы получать нужную часть пакета — одной удобной строчкой с внятным названием — т.е. чтобы вся магия манипуляции с байтами — была не видна при взаимодействии. _NN>>Секунду, вам нужна часть вектора или весь ? _NN>>Невозможно забрать только часть без копирования.
S>Части вектора в виде span — беру пока объект живой и владеет вектором — для удобства. Можно было бы все те же операции проделать до создания объекта — но это не нагладно было бы и не дешевле по ресурсам.
Всё хорошо, возвращаем span и не копируем лишний раз.
Только это не связано с тем, что в C++ называется перемещением.
Здравствуйте, Shmj, Вы писали:
S>Я так и сделал — класс овладевает вектором, получаю нужные элементы вектора с помощью span и примитивных типов. Потом отнимаю владение, дабы не копировать, передаю в другую обертку — и уже эту обертку на выход.
Да вот в том-то и дело, что это нифига не так. Ты, походу, вообще нифига не понял, из того, что я тебе писал.
Тебе здесь уже сто раз могли бы помочь, если бы ты сразу рассказал о конкретной задаче, а не пускался в философию программирования с абстрактными примерами.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Shmj, Вы писали:
S>Просто вы тут любите так написать, чтобы 4-х этажные шаблоны с FINAE, чтоб без поллитра не разобраться. А когда кто-то пишет максимально просто и понятно — считается по-лоховски.
Глупый, шаблоны — это средство, а не самоцель. Никто не станет писать шаблоны просто ради шаблонов. Лично я на любой случай использования шаблона могу детально пояснить, для чего и почему.
Если ли же говорить о твоём конкретном случае, то у тебя просто всё через жопу и дело тут вовсе не в шаблонах. И, как я писал уже, тебе уже сто раз могли бы помочь, если бы ты сразу рассказал о решаемой задаче, а не пускался в философию программирования с абстрактными примерами.
Кроме того, ты совершенно правильно заметил, что "многоэтажность" была характерна для старых версий языка и SFINAE. Сейчас вместо этого используются концепты и улучшенный синтаксис. Сейчас в большинстве случаев можно даже традиционную шаблонную шапку не писать. Я думаю, лопух типа тебя даже не всегда отличит шаблонную функцию от нешаблонной.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, _NN_, Вы писали:
S>>>Части вектора в виде span — беру пока объект живой и владеет вектором — для удобства. Можно было бы все те же операции проделать до создания объекта — но это не нагладно было бы и не дешевле по ресурсам. _NN>>Всё хорошо, возвращаем span и не копируем лишний раз. _NN>>Только это не связано с тем, что в C++ называется перемещением.
S>Перемещение уже производим в самом конце — когда пакет сформирован. Берем у этого класса-пакета байты вектора и конфискуем — передаем далее (в другую обертку).
Перемещение чего ? вектора, который мы держив внутри ?
Вроде такого ?
using namespace std;
class Packet
{
public:
vector<byte>&& release_vector() { return move(bytes); }
span<byte> get_writable_range(size_t from, size_t to)
{
// Ensure size is at least `to`.if (to > bytes.size()) {
bytes.resize(to);
}
return { &bytes[from], &bytes[to] };
}
private:
vector<byte> bytes;
};
Packet p;
auto part1 = p.get_writable_range(1,10);
.. work with part 1
auto part2 = p.get_writable_range(10,20);
.. work with part 2
// Steal ownership
vector<byte> result = p.release_vector();
// work with result
В таком варианте неясно зачем нужно вытаскивать вектор, и не работать просто с объектом Packet.
Здравствуйте, Shmj, Вы писали:
S>У меня изначально создается нужного размера вектор в конструкторе, частично заполняю данными. Но для удобства часть данных вносятся позже через сеттеры. Там где нужно сделать преобразование бинарных данных — доступ через span без выделения памяти.
Я к тому, что если есть два относительно больших вектора, то слить их в один другой вектор без копирования не получится никак.
Перемещение здесь ничем не поможет.
Я сам делал нечто подобное много лет назад, с бинарными сетевыми пакетами.
Там у меня были большие данные и маленький заголовок пакета фиксированного размера.
Если данные формируются где-то на стороне, то выдаем этой стороне вектор с зарезервированным местом для заголовка, а потом перемещаем себе и формируем заголовок.
Когда move-семантики не было, то это через swap делалось, но суть та же.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, _NN_, Вы писали:
_NN>>В таком варианте неясно зачем нужно вытаскивать вектор, и не работать просто с объектом Packet.
R>Это не первый его пост в таком духе. Он никогда не рассматривает вариант, что в его решении что-то может быть не совсем идеально, поэтом сразу переходит к обсуждению глобальных проблем С++ на абстрактных примерах.
Ну подождём ответа на конкретный вопрос
Как я писал выше, для работы с бинарными данными вообще ничего не нужно писать, а генерировать через Kaitai Struct.
Мало того, что сразу получаем код для всех языков, так и ещё всё описано декларативно.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, _NN_, Вы писали:
_NN>>В таком варианте неясно зачем нужно вытаскивать вектор, и не работать просто с объектом Packet.
S>Там получается есть тело — у него один формат. А есть общий пакет. Так вот это тело формируем и передаем уже далее, в обертку общего пакета.
Ничего не понятно, что это означает.
Вы код покажите.
А то неясно это вот так:
"Есть один формат:"
class Body { vector<bytes> b; };
"Есть общий пакет:"
class Packet { vector<bytes> b; };
Body b;
Packet p;
p.b.insert(p.begin(), b.begin(), b.end()); // copy
В таком варианте придётся копировать данные из Body в Packet.
Или так?
"Есть один формат:"
class Body { span<bytes> b; };
"Есть общий пакет:"
class Packet { vector<bytes> b; };
Packet p;
Body b = span(p.b.begin(), p.b.begin() + 10);
Здравствуйте, Shmj, Вы писали:
S>Только лишь по одной причине — мы люди и нам удобнее код разбивать на функции, удобнее манипулировать объектами — так легче управлять. S>Если бы не наша человечность и свойственные нашей психике паттерны восприятия — то не нужно было бы разбивать код на множество функций и создавать объекты — все бы писалось одним большим куском в машинных кодах. S>Это можно было бы оформить в виде функции — но так не удобно. Т.е. типа передаем вектор в функцию, эта функция последовательно устанавливает части вектора, основываясь на неких индексах. Но так не наглядно — все индексы перенесены в класс-обертку а функция просто получает части этого пакета у удобном виде по названию, не думая об индексах и смещениях. S>Тут нет машины состояний — по прежнему просто доступ к частям массива, просто удобным образом. S>А как же. Это для удобства восприятия. Как и разбиение на функции — можно было бы не разбивать, все писать в одной большой функции — зачем плодить, если можно в одной? S>Чтобы было наглядно и удобно — не работать с индексами а лишь получать удобно читаемые названия.
Перевожу на человеческий язык: "Мой код идеален, в нём нет недостатков. А в том, что получается говно, виноват С++, потому что он не C#".
--
Справедливость выше закона. А человечность выше справедливости.