Собственно хочу создать через new массив элементов T без вызова их конструкторов, чтобы потом по требованию делать std::construct_at() только для отдельных элементов .
Если поместить этот T в union для отложенной инициализации, и у T есть конструктор по умолчанию не дефоолтный, то стандарт требует добавление в union тоже конструктора по умолчанию, даже если тот ничего не делает... Зачем так сделано?
Тобишь создам через new массив 50 элементов union и для каждого будет вызван конструктор в процессе.. Это же замедление!
Подскажите как лучше. Использовать aligned_storage нет желания, и reinterprete_cast тоже.
Здравствуйте, Sm0ke, Вы писали:
S>Собственно хочу создать через new массив элементов T без вызова их конструкторов, чтобы потом по требованию делать std::construct_at() только для отдельных элементов .
S>Подскажите как лучше. Использовать aligned_storage нет желания, и reinterprete_cast тоже.
Вот прямо так, как ты описал, не получится — инициализация массива предполагает инициализацию каждого его элемента. Но ты можешь получить очень близкую функциональность при помощи std::vector и двух его функций-членов — reserve и emplace_back. Эскизно так:
std::vector<MyItem> v;
v.reserve(50); // резервируем память под 50 элементов, без создания самих самих элементов.
assert(v.size() == 0);
v.emplace_back(arg1, arg2, arg3); // конструируем элементы прямо по месту выделенной памяти, без промежуточных копирований и перемещений
v.emplace_back(arg1, arg2, arg3); // фактически внутри вектора используется placement new, только скрыто от программиста
v.emplace_back(arg1, arg2, arg3);
assert(v.size() == 3);
Здравствуйте, Sm0ke, Вы писали:
S>Собственно хочу создать через new массив элементов T без вызова их конструкторов, чтобы потом по требованию делать std::construct_at() только для отдельных элементов .
Массив std::optional<T> не нравится? Хочется сэкономить на булевых флажках? Или нужна непрерывность адресов элементов?
S>Если поместить этот T в union для отложенной инициализации, и у T есть конструктор по умолчанию не дефоолтный, то стандарт требует добавление в union тоже конструктора по умолчанию, даже если тот ничего не делает... Зачем так сделано?
Это сделано для определённости. Чтобы было понятно, какие члены живут в юнионе "из коробки".
S>Тобишь создам через new массив 50 элементов union и для каждого будет вызван конструктор в процессе.. Это же замедление!
S>Подскажите как лучше. Использовать aligned_storage нет желания, и reinterprete_cast тоже.
Ну заведи юнион, у которого будет первый член с тривиальным конструктором — какой-нибудь std::monostate dummy
union TrulyUnion {
std::monostate dummy = {};
T data;
.....
};
Здравствуйте, Sm0ke, Вы писали:
S>Подскажите как лучше. Использовать aligned_storage нет желания, и reinterprete_cast тоже.
Кстати, зря.
Твой union ни при каких условиях не будет trivially constructible (даже если конструкторы там выродятся, даже без оптимизации — как в моём ответе с инициализированным monostate), это может пессимизировать код, который смотрит на эти признаки.
Ну и в любом случае, там будет какое-то опосредование, обращение к члену:
union U {
.....
T t;
.....
};
U* arr = new U[50];
std::construct_at(&arr[0].t);
arr[0].t.foo();
так почему бы не сделать этот член как функцию
struct U {
std::aligned_storage_t<sizeof(T), alignof(T)> buf;
constexpr T& t() { return (T&)buf; }
constexpr const T& t() const { return (const T&)buf; }
// и даже перетащить сюда нужное из исходного типаauto foo() { return t().foo(); }
};
Здравствуйте, Sm0ke, Вы писали:
S>Тобишь создам через new массив 50 элементов union и для каждого будет вызван конструктор в процессе.. Это же замедление!
Наверное, если сделать его инлайновым и пустым, никакого вызова фактически генерироваться не будет, а значит не будет и замедления.
Re[2]: union требует конструктор, зачем так сделано?
Здравствуйте, Кодт, Вы писали:
К>Массив std::optional<T> не нравится? Хочется сэкономить на булевых флажках? Или нужна непрерывность адресов элементов?
Непрерывность не важна. Флажки занимают память, плюс будут вызваны конструкторы у эдементов optional
К>Ну заведи юнион, у которого будет первый член с тривиальным конструктором — какой-нибудь std::monostate dummy
К>
Здравствуйте, rg45, Вы писали:
R>Вот прямо так, как ты описал, не получится — инициализация массива предполагает инициализацию каждого его элемента. Но ты можешь получить очень близкую функциональность при помощи std::vector и двух его функций-членов — reserve и emplace_back. Эскизно так:
R>
R>std::vector<MyItem> v;
R>v.reserve(50); // резервируем память под 50 элементов, без создания самих самих элементов.
R>assert(v.size() == 0);
R>v.emplace_back(arg1, arg2, arg3); // конструируем элементы прямо по месту выделенной памяти, без промежуточных копирований и перемещений
R>v.emplace_back(arg1, arg2, arg3); // фактически внутри вектора используется placement new, только скрыто от программиста
R>v.emplace_back(arg1, arg2, arg3);
R>assert(v.size() == 3);
R>
Да, я думал об этом. Но было бы странно делая самописный вектор иметь стандартный под капотом)
Ещё думал про std::aligned_alloc() из <cstdlib> но в студии community его нет (на сайте написано, что микрософт его не поддерживает, тк он не дружит с их free).
Есть ещё конечно сишный aligned_alloc() без std:: ...
Пока остановился на вызове ::operator new(s_size * p_reserve, s_align, std::nothrow), пришлось писать reinterprete_cast<>()
Вот такая получилась заготовочка:
Выделение и освобождение памяти тут через указатели на функцию, чтобы этот вектор можно было передавать в dll (надеюсь, что такое решение рабочее).
Чем не устраивает стандартный вектор? При перерезервировании он делает copy или move конструирование элементов (что нежелательно для типов, которые я собираюсь в нём хранить). Ещё при инсерте делает move assignment.
Планирую использовать memcpy() при перерезервировании, и memmove() при инсерте/удалении. Ещё добавлю полиси роста как шаблонный параметр, чтобы при добавлении — резерв увеличивался не как в std::vector<> в 2 раза, а на фиксированный шаг. Или задавать свои полиси роста.
Изначальная задача вообще в следующем — Нужен контейнер на подобии стэка, чтобы:
добавлять в конец или редко на предпоследнюю позицию.
удалять последние N элементов.
иметь быстрый доступ к последним 3-м элементам.
Может стОит взять std::forward_list<> и работать с началом списка? Или какие есть ещё варианты?
p.s: Число хранимых элементов в теории не превышает 20-30, но может быть что угодно, ведь это для интерпретатора. Каждый элемент хранит 2 указателя и итератор от std::list<>
Здравствуйте, Sm0ke, Вы писали:
S>Да, я думал об этом. Но было бы странно делая самописный вектор иметь стандартный под капотом) S> . . . S>Пока остановился на вызове ::operator new(s_size * p_reserve, s_align, std::nothrow), пришлось писать reinterprete_cast<>()
По-моему, не стоит так уж избегать reinterpret_cast в данном случае, поскольку его использование инкапсулировано, локализовано и должным образом оттестировано, а не размазано по клиентскому коду.
P.S. И, возможно, даже имеет смысл заменить reinterpret_cast на двойной static_cast (с промежуточным преобразованием к void*) — чтобы следовать принципу использования преобразования наименьшей достаточной силы.
Здравствуйте, rg45, Вы писали:
R>По-моему, не стоит так уж избегать reinterpret_cast в данном случае, поскольку его использование инкапсулировано, локализовано и должным образом оттестировано, а не размазано по клиентскому коду.
Спасибо
R>P.S. И, возможно, даже имеет смысл заменить reinterpret_cast на двойной static_cast (с промежуточным преобразованием к void*) — чтобы следовать принципу использования преобразования наименьшей достаточной силы.
Почему двойной? ::operator new() итак возвращает void *
И в этом фрагменте std::nothrow версия, следовательно new может вернуть null pointer. Можно ли кастить null pointer через static_cast из void *?
Здравствуйте, Sm0ke, Вы писали:
S>Почему двойной? ::operator new() итак возвращает void * S>И в этом фрагменте std::nothrow версия, следовательно new может вернуть null pointer. Можно ли кастить null pointer через static_cast из void *?
Видимо, я просто не совсем правильно представил сценарий, в котором используется преобразование. Сбило с толку упоминание о reinterpret_cast — оно, выходит, избыточно в данном случае, достаточно static_cast. Конечно, для нулевых указателей преобразование тоже будет работать.
Вообще, этот фрагмент настораживает. Здесь ты выполняешь преобразование указаетля на просто кусок памяти к указателю на объект, что небезопасно в общем случае. Если Т — это нетривиально конструируемый тип, то время жизни объекта в этой точке еще не началось и любая попытка обращения к этому объекту порождает неопределенное поведение. Использовать данный кусок памяти как объект типа Т можно будет только после выполнения placement new. Но в таком случае и никаких преобразований не понадобится, ибо placement new сразу возвратит указатель нужного типа.
R>Вообще, этот фрагмент настораживает. Здесь ты выполняешь преобразование указаетля на просто кусок памяти к указателю на объект, что небезопасно в общем случае. Если Т — это нетривиально конструируемый тип, то время жизни объекта в этой точке еще не началось и любая попытка обращения к этому объекту порождает неопределенное поведение. Использовать данный кусок памяти как объект типа Т можно будет только после выполнения placement new. Но в таком случае и никаких преобразований не понадобится, ибо placement new сразу возвратит указатель нужного типа.
placement new будет в методе emplace_back(). А при m_count == 0 не должно быть обращений к непроинициализированной памяти.
Конструктор лишь резервирует память под массив. Создание объектов там будет по требованию.
Вот вопрос: как лучше написать метод insert(), emplace() чтобы было exception safety? Если конструктор элемента кинет исключение.
Вариант 1: Раздвигать последующие элементы, placement new, ловить исключение (сдвигать обратно и рефровать), обновлять count когда нет исключения.
Вариант 2: В отдельном месте placement new, потом memcpy в раздвинутое место массива.
Вариант 3: ...
В варианте 1 возможно изменение capacity даже при исключении. В варианте 2 при исключении capacity прежнее.
S>>Вот вопрос: как лучше написать метод insert(), emplace() чтобы было exception safety? Если конструктор элемента кинет исключение.
R>забавно смотреть как народ изобретает vector еще раз
Мне нужен вектор, который можно передать в dll и который не вызывает лишний раз операторы копирования/мува
S>Выделение и освобождение памяти тут через указатели на функцию, чтобы этот вектор можно было передавать в dll (надеюсь, что такое решение рабочее).
бред
уже придумали без вас аллокаторы
S>Чем не устраивает стандартный вектор? При перерезервировании он делает copy или move конструирование элементов (что нежелательно для типов, которые я собираюсь в нём хранить). Ещё при инсерте делает move assignment.
бред
разберитесь когда они не используются
хотя бы сорсы посмотрите
S>Планирую использовать memcpy() при перерезервировании, и memmove() при инсерте/удалении. Ещё добавлю полиси роста как шаблонный параметр, чтобы при добавлении — резерв увеличивался не как в std::vector<> в 2 раза, а на фиксированный шаг. Или задавать свои полиси роста.
S>>Планирую использовать memcpy() при перерезервировании, и memmove() при инсерте/удалении. Ещё добавлю полиси роста как шаблонный параметр, чтобы при добавлении — резерв увеличивался не как в std::vector<> в 2 раза, а на фиксированный шаг. Или задавать свои полиси роста.
R>это и обычный vector делает с кастом аллокатором
Здравствуйте, reversecode, Вы писали:
S>>Чем не устраивает стандартный вектор? При перерезервировании он делает copy или move конструирование элементов (что нежелательно для типов, которые я собираюсь в нём хранить). Ещё при инсерте делает move assignment.
R>бред R>разберитесь когда они не используются R>хотя бы сорсы посмотрите
При изменении capacity стандартный вектор вызывает copy конструкторы элементов в новом месте. Если нет copy конструктора, то вызывает move конструкторы.
При удалении не с конца будут вызваны операторы move присваивания для оставшихся элементов после удаляемого.
Разве нет?
Мне достаточно просто делать memmove() или memcpy() для элементов, которые я собираюсь в нём хранить.