Корректный способ инициализировать динамический массив в C++

Я в настоящее время работаю над проектом C++, где динамические массивы часто появляются. Я задавался вопросом, каков мог быть корректный способ инициализировать динамический массив с помощью нового оператора? Мой коллега сказал мне, что это нет - нет для использования новый в конструкторе, так как конструктор является конструкцией, которая не должна быть подвержена ошибкам или не должна перестать работать вообще, соответственно. Теперь давайте рассмотрим следующий пример: у Нас есть два класса, более или менее сложное состояние класса и класс StateContainer, который должен быть самообъяснен.

class State {
private:
 unsigned smth;
public:
 State();
 State( unsigned s );
};

class StateContainer {
private:
 unsigned long nStates;
 State *states;
public:
 StateContainer();
 StateContainer( unsigned long n );
 virtual ~StateContainer();
};

StateContainer::StateContainer() {
 nStates = SOME_DEFINE_N_STATES;
 states = new State[nStates];
 if ( !states ) {
  // Error handling
 }
}

StateContainer::StateContainer( unsigned long n ) {
 nStates = n;
 try {
  states = new State[nStates]
 } catch ( std::bad_alloc &e ) {
  // Error handling
 }
}

StateContainer::~StateContainer() {
 if ( states ) {
  delete[] states;
  states = 0;
 }
}

Теперь на самом деле у меня есть два вопроса:

1.) Это в порядке, для вызова новым в конструкторе, или лучше создать дополнительный init () - Метод для Массива состояния и почему?

2.) Что является лучшим способом проверить, успешно выполнился ли новый:

if (!ptr) std::cerr << "new failed."

или

try { /*new*/ } catch (std::bad_alloc) { /*handling*/ } 

3.) Хорошо его три вопроса; o) Под капотом, новым, делает своего рода

ptr = (Struct *)malloc(N*sizeof(Struct));

И затем вызовите конструктора, правильно?

7
задан mefiX 30 April 2010 в 07:47
поделиться

6 ответов

Вы должны разрешить std :: bad_alloc ] распространяться - в любом случае, вы вряд ли сможете сделать ничего разумного.

Прежде всего, вызов исключения из конструктора - единственный надежный способ сообщить о проблеме - если нет исключения, это означает, что объект полностью построен. Так что перехват только std :: bad_alloc не поможет против других возможных исключений.

Тогда что вы можете сделать, чтобы «обработать» это таким образом, чтобы другой код знал об этом и мог соответствующим образом реагировать?

Правильно используйте исключения - пусть они распространяются на сайт, где они могут быть разумно обработаны .

7
ответ дан 6 December 2019 в 15:20
поделиться
  1. Если вы ищете возвращаемый тип, т.е. если функция должна возвращать статус, используйте отдельную функцию (init ()) для выделения памяти.

    Если вы проверяете, собираетесь ли вы проверять, выделена ли память, проверяя условие NULL во всех функциях-членах, то выделите память в самом конструкторе.

  2. Обработка исключений (например, try ... catch) - лучший выбор.

  3. Помимо вызова конструктора также инициализируется указатель this.

0
ответ дан 6 December 2019 в 15:20
поделиться

Короткий ответ:
Нет, ваш друг ошибается. Конструктор - это место, где вы выполняете распределение + инициализацию. У нас даже есть термин "Приобретение ресурсов - это инициализация" (RAII)... классы приобретают ресурсы как часть своей инициализации в конструкторе, а классы освобождают приобретенные ресурсы в своих деструкторах.

Длинный ответ:

Рассмотрим следующий код в конструкторе:

member1  = new whatever1[n];
member2  = new whatever2[m];

Теперь, предположим, что в вышеприведенном случае второе выделение должно было вызвать исключение, либо потому что конструирование whatever2 не удалось и вызвало исключение, либо выделение не удалось и вызвало std::bad_alloc. В результате память, которую вы выделили для whatever1, будет утечена.

По этой причине лучше использовать контейнерный класс, такой как std::vector:

MyClass::MyClass(std::size_t n, std::size_t m) : member1(n), member2(m) {}
// where member1 and member2 are of type std::vector

При использовании типа std::vector, если второе выделение не удастся, будет вызван деструктор предыдущего std::vector, в результате чего ресурсы будут соответствующим образом освобождены. Возможно, именно это имел в виду ваш друг, когда говорил, что вам не следует использовать new (вместо этого вам следует использовать класс-контейнер), и в этом случае он был бы в основном прав... хотя есть классы умных указателей, такие как boost::shared_ptr, которые предоставляют вам те же гарантии безопасности, и где вам все равно придется использовать new, так что он все еще не совсем прав.

Обратите внимание, что если у вас есть только один объект/массив, который вы выделяете, то это не проблема... что и происходит в вашем коде... вам не нужно беспокоиться об утечках из-за неудачного выделения. Также я должен добавить, что new будет либо успешным, либо выбросит исключение; он не вернет NULL, и поэтому эта проверка бессмысленна. Единственный случай, когда вы должны поймать std::bad_alloc - это случай, когда вы были вынуждены выполнить несколько выделений (вам запрещено использовать std::vector), в этом случае вы деаллоцируете другие ресурсы в обработчике, но затем вы повторно отбрасываете исключение, потому что вы должны позволить ему распространиться.

1
ответ дан 6 December 2019 в 15:20
поделиться

Ваш друг в чем-то прав. Однако обычной практикой является резервирование памяти в конструкторе и освобождение памяти в деструкторе.

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

Решением этой проблемы является создание / распространение исключения. Исключение всегда проходит и может быть обработано вызывающим.

0
ответ дан 6 December 2019 в 15:20
поделиться

Не полный ответ, просто мои 2 копейки:

1: я бы использовал new в конструкторе, хотя для динамических массивов лучше использовать STL.

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

3: не забудьте про new-оператор, чтобы сделать историю немного интереснее.

1
ответ дан 6 December 2019 в 15:20
поделиться

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

То, что предлагает ваш друг, приводит к неуправляемому и подверженному ошибкам коду и идет вразрез со всеми хорошими практиками C ++. Он не прав.

Что касается динамических массивов, используйте вместо них std :: vector . А для его инициализации просто передайте параметр в конструктор:

std::vector<int>(10, 20)

создаст вектор из 10 целых чисел, все из которых инициализированы значением 20.

5
ответ дан 6 December 2019 в 15:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: