Daily bit(e) C++. std::uninitialized_copy, std::uninitialized_fill, std::uninitialized_move, std::uninitialized_value_construct, std::uninitialized_default_construct, std::destroy
Daily bit(e) C++ #230, uninitialized-алгоритмы, которые могут создавать и уничтожать объекты в необработанных (неинициализированных) блоках памяти.
Ручное управление временем жизни и создание объектов внутри нетипизированных блоков памяти – очень нишевая тема.
Однако бывают ситуации, когда std::vector
недостаточно.
К счастью, стандартная библиотека C++ предлагает набор uninitialized-алгоритмов, которые обеспечивают конструирование по умолчанию, копирующее и перемещающее конструирования, конструирование значением, а также уничтожение поверх необработанной (неинициализированной) памяти.
#include <vector>
#include <string>
#include <memory>
std::vector<std::string> src{"Hello", "World!"};
{
void* buffer = std::aligned_alloc(alignof(std::string),
sizeof(std::string) * src.size());
if (buffer == nullptr) std::abort();
auto raw_it = static_cast<std::string*>(buffer);
{ // копирующее конструирование
auto end_it = std::uninitialized_copy(src.begin(), src.end(), raw_it);
// subrange(raw_it,end_it) == {"Hello", "World!"}
// Ручное создание требует ручного уничтожения
std::destroy(raw_it, end_it);
}
{ // копирующее конструирование из одиночного значения
auto end_it = raw_it + src.size();
std::uninitialized_fill(raw_it, end_it, std::string("Something"));
// subrange(raw_it,end_it) == {"Something", "Something"}
// Ручное создание требует ручного уничтожения
std::destroy(raw_it, end_it);
}
{ // (C++17) перемещающее конструирование
auto end_it = raw_it + src.size();
std::uninitialized_move(src.begin(), src.end(), raw_it);
// subrange(raw_it,end_it) == {"Hello", "World!"}
// src == {"", ""}
// Ручное создание требует ручного уничтожения
std::destroy(raw_it, end_it);
}
// Освобождение буфера
std::free(buffer);
}
{ // (C++20) конструирование значением и по умолчанию
constexpr size_t size = 7;
void* buffer = std::aligned_alloc(alignof(int),
sizeof(int) * size);
if (buffer == nullptr) std::abort();
auto raw_it = static_cast<int*>(buffer);
auto end_it = raw_it + size;
// Конструирование значением
// (для типов POD это означает нулевую инициализацию)
std::uninitialized_value_construct(raw_it, end_it);
// subrange(raw_it, end_it) == {0, 0, 0, 0, 0, 0, 0}
// Для следующего примера
*raw_it = 42;
// Ручное создание требует ручного уничтожения
std::destroy(raw_it, end_it);
// Конструирование по умолчанию
// (для типов POD это означает нулевую инициализацию)
std::uninitialized_default_construct(raw_it, end_it);
// Технически содержимое представляет собой неопределенные значения.
// На практике данные обычно не трогаются.
// subrange(raw_it, end_it) == {42, 0, 0, 0, 0, 0, 0}
// Если вы хотите положиться на это поведение,
// обратитесь к информации от разработчика компилятора.
// Ручное создание требует ручного уничтожения
std::destroy(raw_it, end_it);
// Освобождение буфера
std::free(buffer);
}
Открыть пример на Compiler Explorer.