Std::vector и новое размещение для передачи массива const с размером, известным во время выполнения

У меня есть два метода создания списка объектов во время выполнения, у которых есть потенциальные недостатки, которые я хотел бы рассмотреть. В конечном счете мне интересно, какой еще e

Критерии моей проблемы:

  • Один основной объект будет содержать фиксированный набор второстепенных объектов (технически представляющих собой двумерный массив).
  • Не желайте реализовывать конструктор по умолчанию для второстепенного объекта, поскольку он будет иметь некоторый константный параметр, который назначается посредством пользовательского ввода (значение по умолчанию может привести к недопустимым состояниям в другом месте программы!)
  • Для выполнения программы потребуется очень мало (в большинстве случаев 1) основного объекта.
  • Состояние каждого второстепенного объекта, вероятно, будет зависеть от некоторого пользовательского ввода (вероятно, в форме файла конфигурации).

Мое понимание:

  • Std::vector, вероятно, более понятен в том, как создаются объекты, но я не хочу поощрять изменение размера массива, который, как я считаю, является несколько подразумеваемой функцией любого std::vector.
  • Кажется, что новое размещение успешно создает пул объектов по желанию, не поощряя увеличение или уменьшение размера массива, поскольку для этого нет функции по умолчанию.
  • Я не вижу никакой разницы в нисходящем использовании, поэтому склоняюсь к размещению нового, несмотря на менее частый синтаксис, поскольку он препятствует изменению размера нисходящего потока.

Соответствует ли это рассуждение намерению разместить новое или мне не хватает более подходящего стандартного контейнера?

В приведенном ниже примере параметры списка объектов 1 и 2 представляют собой подход на основе std::vector и новое размещение соответственно. Для простоты я исключил любую обработку исключений. Идеальное использование в нисходящем направлении (например, без изменения размера) выглядит для меня одинаково (доступ через оператор []).

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

#include <vector>

class minorObj{
    const int value;
    public:
    minorObj() = delete;
    minorObj(int x):value(x){}
};



class majorObj{
    const int N;
    public:
    std::vector<minorObj> objList_op1;
    minorObj*            objList_op2;
    majorObj(int size):N(size){
        objList_op1.reserve(N);
        objList_op2 = static_cast<minorObj*>(operator new[](N * sizeof(minorObj)));
        for(int i = 0; i<N;i++){
            objList_op1.emplace_back(minorObj(i));
            new(&objList_op2[i]) minorObj(i);
        }
    }
    ~majorObj(){
        for(int i = N -1; i >= 0; i--){
            objList_op2[i].~minorObj();
        }
    }
};

void someFn(){majorObj(25);} //added some different scope to emphasize need for proper memory mgmt

int main(){
    someFn();
    return 0;
}

Как насчет того, чтобы обернуть std::vector в класс, который предоставляет только функции, которые не могут изменить свой размер? Примечание: этот majorObj сгорит, как только будет скопирован.

Quentin 18.11.2022 21:03

Почему objList_op1 общедоступен в примере? Разве приватность не решит вашу проблему с изменением размера?

Nelfeal 18.11.2022 21:06

Если вам нужен массив фиксированного размера, просто используйте std::array.

Jesper Juhl 18.11.2022 21:07

«Не желайте реализовывать конструктор по умолчанию для второстепенного объекта, поскольку он будет иметь некоторый параметр const, который назначается с помощью пользовательского ввода». Обычно это нежелательный подход, поскольку он приводит к осложнениям, подобным тому, который задается здесь. Вместо этого инварианты класса можно поддерживать с помощью инкапсуляции и сеттеров/геттеров.

François Andrieux 18.11.2022 21:09

Примечание: за 25 с лишним лет работы профессиональным разработчиком я никогда не сталкивался с ситуацией, когда размещение new было бы правильным решением проблемы — я бы очень скептически отнесся к этому пути.

Jesper Juhl 18.11.2022 21:10

@FrançoisAndrieux Я бы не согласился. Моделирование пустого/недопустимого состояния для объектов, которые логически не должны иметь такого состояния, усложняет инварианты.

Quentin 18.11.2022 21:12

@JesperJuhl OP явно передает размер конструктору во время выполнения.

Nelfeal 18.11.2022 21:15

@Quentin Объект не обязательно должен иметь значение NULL, чтобы извлечь выгоду из семантики значений. Если вы решите, что сконструированный по умолчанию экземпляр эквивалентен перемещенному экземпляру, имеет смысл сделать большинство типов конструируемыми по умолчанию. Этот вопрос является хорошим примером, точно так же, как после перемещения у вас есть объект шелухи, иногда вам нужно место для размещения значения до того, как значение будет готово для предоставления. Иногда объект не представляет никакого конкретного значения, независимо от того, находится ли то, что он представляет, в состоянии логической пустоты или нет.

François Andrieux 18.11.2022 21:28

@JesperJuhl не нужно знать размер во время компиляции для std::array? В противном случае я бы пошел с этим.

20lbpizza 18.11.2022 21:28

@Nelfeal В реальном коде он является частным, но это не имеет значения, поскольку я не хочу, чтобы majorObj менял размер своего собственного массива.

20lbpizza 18.11.2022 21:34

@20lbpizza Разве ты не пишешь majorObj? Зачем вам делать то, чего вы не хотите? Моргни дважды, если кто-то заставляет тебя.

Nelfeal 18.11.2022 21:36

Возможно, вы хотите что-то вроде fixed_capacity_vector (или static_vector boost? Непонятно, чего именно вы хотите достичь.

Nelfeal 18.11.2022 21:43

@Quentin Да, класс-оболочка, вероятно, добьется цели и практически не потребует накладных расходов, если я не ошибаюсь? Я мог бы просто раскрыть функциональность std::vector, которую мы хотим поддерживать, и на этом закончить. Гораздо проще, чем иметь дело с этим распределением в стиле C (которое, как вы уже указали, не является полным в этом примере)!

20lbpizza 18.11.2022 21:43

@Nelfeal fixed_capacity_vector выглядит как потенциальная альтернатива, но похоже, что вы можете удалить. По сути, мне нужен вектор с очень ограниченной функциональностью.

20lbpizza 18.11.2022 21:55
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
14
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Основываясь на комментарии Квентина выше, пример реализации класса-оболочки можно выполнить с помощью следующего класса шаблона. Это позволяет использовать преимущества инициализации во время выполнения std::vector, разрешая/запрещая другие операции по мере необходимости. Доступ к содержащимся в нем объектам будет таким же через оператор нижнего индекса.

template <class T>
class RuntimeArray{
    private:
    std::vector<T> values;
    public:
    RuntimeArray() = delete;
    RuntimeArray(const std::vector<int>& inputs){ // can be whatever type to initialize
        values.reserve(inputs.size());
        for(int i = 0; i < inputs.size(); i++){
            values.emplace_back(inputs[i]);
        }
    }
    T& operator [](int i){
        return values[i];   
    }
    const T& operator[](int i) const{
        return values[i];
    }
    // std::vector without std::vector::push_back or std::vector::erase etc.
};

Пара замечаний: конструктор должен использовать inputs как константную ссылку, чтобы избежать копирования. emplace_back должен взять inputs[i] напрямую, чтобы создать T на месте. Также должна быть const версия operator[].

Ranoiaetep 19.11.2022 00:59

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