У меня есть класс Mesh
, который я хочу владеть несколькими различными «массивными» структурами для vertices
, indices
, vertex_normals
и т. д.
Я бы хотел, чтобы класс Mesh
был полностью независимым от источника этих форматов, так как у меня есть много форматов 3D, которые мне нужно поддерживать. Таким образом, в настоящее время у меня есть набор функций загрузчика, которые считывают 3D-данные из определенного формата, создают сетку и назначают ее экземпляру Model
(который содержит unique_ptr
для определенного Mesh
).
В настоящее время я делаю это, просто выделяя массивы в куче внутри функции загрузчика, а затем передавая указатели на эти массивы в конструктор Mesh
. Но кажется, что использование std::vector
или std::unique_ptr
было бы более безопасным вариантом. Быстрый макет того, как может выглядеть одна из функций загрузчика (в частности, предназначенная для загрузки из файла OBJ), выглядит следующим образом:
void load_OBJ(std::shared_ptr<Model> model, std::string file_path){
# Read the 3d data
...
# Create the Mesh object
std::unique_ptr<Mesh> mesh(new Mesh());
# Add the 3d data to the Mesh (???)
mesh->set_vertices( ??? );
mesh->set_indices( ??? );
...
# Assign the Mesh object to the Model:
model->set_mesh(mesh);
};
Моя первая интуитивная реакция заключалась в том, что, поскольку Mesh
сохранит эксклюзивное право собственности на всех своих членов, я должен использовать std::unique_ptr
, так что что-то вроде этого:
class Mesh {
public:
std::unique_ptr<Vector3[]> vertices;
uint32_t num_vertices;
Mesh() { };
~Mesh() { };
void set_vertices(std::unique_ptr<Vector3[]>& new_vertices, uint32_t num_vertices){
this->vertices = std::move(new_vertices);
this->num_vertices = num_vertices;
};
Но потом я начал задаваться вопросом, а не лучше ли std::vector
. В этом контексте скорость доступа к данным из Mesh
будет чрезвычайно важна. Мои грубые тесты (с оптимизацией компилятора) намекают мне, что доступ к данным из std::unique_ptr<Vector3[]>
и std::vector<Vector3>
практически неотличим. Так что я не уверен, есть ли здесь что-то еще, что мне не хватает?
@wohlstad Я полагаю, что моя главная проблема заключается в том, что дубликат std::vector
создается при передаче его в mesh->set_vertices()
, тогда как с std::unique_ptr
я могу гарантировать, что когда-либо существует только одна копия данных вершины, и право собственности на нее ограничено исключительно родительским Mesh
объектом . Но, возможно, это не очень обоснованное беспокойство.
Дубликат делать не обязательно. Я думаю что-то вроде void set_vertices(std::vector<Vector3>&& new_vertices)
Если вы думаете в другом направлении, покажите, что вы пробовали в вопросе.
С std::unique_ptr<x[]>
много проблем. Нет информации о размере (вы должны ее поддерживать), нет неявной копии (только перемещение). Это не дружественно к итератору. Трудно изменить размер (добавить удалить элементы). Единственный выигрыш в том, что размер указателя будет 8 байт, а размер std::vector
— 24 байта (внутри это 3 указателя).
Доступ к данным в std::unique_ptr<Vector3[]>
или std::vector<Vector3>
должен быть неразличим. Однако vector
занимает немного больше места (capacity
). Вам не нужно беспокоиться о копировании, если вы это сделаете void set_vertices(std::vector<Vector3>&& new_vertices) { vertices = std::move(new_vertices); }
Вы можете использовать семантику перемещения, чтобы избежать копирования std::vector
. И std::unique_ptr
в любом случае требует единоличного владения, поэтому вам также пришлось использовать std::move
.
Ах, я не уверен, почему мне не пришло в голову использовать std::move
на векторе. Это действительно делает эту победу явной для std::vector
. Спасибо!
std::vector
дает вам эксклюзивное право владения массивом элементов с динамическим размером, а это именно то, что вы хотите сделать. Это также упрощает работу, позволяя вам сэкономить на подверженной ошибкам явной переменной размера. Я бы сказал, давай.
Использование вектора также не приведет к большим накладным расходам: это просто указатель на некоторую память с добавленными size
и capacity
. (На практике они также реализованы как указатели, поэтому вектор — это всего лишь три указателя.)
Обязательно сначала вызовите reserve
с соответствующим размером, если вы собираетесь push_back
в вектор:
vertices.reserve(numVertices);
for (size_t i = 0; i < numVertices; ++i) {
// load vertex
vertices.push_back(vertex);
}
В противном случае политика роста вектора по умолчанию может сделать его больше, чем вам нужно, что приведет к трате памяти. (Если я правильно помню, размер вектора по умолчанию удваивается всякий раз, когда его выделенное хранилище заполняется.)
Если вы хотите memcpy
в вектор, вы должны resize
сначала выделить соответствующий объем памяти.
Чтобы передать вектор без создания копий, используйте std::move
. Это сведется к тем же операциям, что и ваш unique_ptr
— просто перетасовке пары указателей. Вы можете легко скопировать вектор, когда захотите, но это не обязательно.
Если вы хотите создать функцию или конструктор, который «потребляет» вектор (берет его во владение), просто возьмите его по значению и переместите в конечный пункт назначения:
void Model::setMesh(std::vector<Vertex> vertices) {
m_mesh = std::move(vertices);
}
std::vector<Vertex> v = ...;
model->setMesh(std::move(v));
Никакая копия никогда не создается там, если вы вызываете функцию с временным (или перемещенным) объектом. Однако, если вы вызовете его с lvalue, он сделает копию и оставит исходный объект нетронутым. Это означает, что вы можете выбрать, следует ли копировать или перемещать что-либо в функцию.
ИМХО
std::vector<Vector3>
идеально подходит.