Должен ли я использовать std::vector или std::unique_ptr здесь?

У меня есть класс 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> практически неотличим. Так что я не уверен, есть ли здесь что-то еще, что мне не хватает?

ИМХО std::vector<Vector3> идеально подходит.

wohlstad 08.02.2023 19:20

@wohlstad Я полагаю, что моя главная проблема заключается в том, что дубликат std::vector создается при передаче его в mesh->set_vertices(), тогда как с std::unique_ptr я могу гарантировать, что когда-либо существует только одна копия данных вершины, и право собственности на нее ограничено исключительно родительским Mesh объектом . Но, возможно, это не очень обоснованное беспокойство.

Chris Gnam 08.02.2023 19:27

Дубликат делать не обязательно. Я думаю что-то вроде void set_vertices(std::vector<Vector3>&& new_vertices) Если вы думаете в другом направлении, покажите, что вы пробовали в вопросе.

user4581301 08.02.2023 19:29

С std::unique_ptr<x[]> много проблем. Нет информации о размере (вы должны ее поддерживать), нет неявной копии (только перемещение). Это не дружественно к итератору. Трудно изменить размер (добавить удалить элементы). Единственный выигрыш в том, что размер указателя будет 8 байт, а размер std::vector — 24 байта (внутри это 3 указателя).

Marek R 08.02.2023 19:29

Доступ к данным в std::unique_ptr<Vector3[]> или std::vector<Vector3> должен быть неразличим. Однако vector занимает немного больше места (capacity). Вам не нужно беспокоиться о копировании, если вы это сделаете void set_vertices(std::vector<Vector3>&& new_vertices) { vertices = std::move(new_vertices); }

Ted Lyngmo 08.02.2023 19:29

Вы можете использовать семантику перемещения, чтобы избежать копирования std::vector. И std::unique_ptr в любом случае требует единоличного владения, поэтому вам также пришлось использовать std::move.

wohlstad 08.02.2023 19:29

Ах, я не уверен, почему мне не пришло в голову использовать std::move на векторе. Это действительно делает эту победу явной для std::vector. Спасибо!

Chris Gnam 08.02.2023 19:35
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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, он сделает копию и оставит исходный объект нетронутым. Это означает, что вы можете выбрать, следует ли копировать или перемещать что-либо в функцию.

Другие вопросы по теме