5

Я разрабатываю класс std::vector-like для самостоятельных целей обучения, но я столкнулся с трудной проблемой выделения памяти внутри конструкторов.Распределение памяти внутри конструкторов?

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

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

Моя первая мысль заключалась в том, чтобы добавлять предупреждение о компиляции всякий раз, когда вызывается конструктор, напоминая, что конструктор может выйти из строя, и что он должен обязательно обработать его или знать о рисках, связанных с использованием конструктора ,

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

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

Это решение делает интерфейс необычным.

У меня было еще несколько идей, но все они, похоже, повредили «ощущение» интерфейса. Эти идеи:

  • наддува как boost::optional
  • Haskell подобные maybes
  • вынуждающие пользователя, чтобы дать мне пул памяти, что структура уполномочена. Эта идея кажется очень изящной, но я не могу найти чистую/популярную статическую реализацию пула памяти для C/C++. Однако я успешно прошел тест на https://github.com/dmitrymakhnin/MemoryPools.
  • использовать описательные утверждения, извиняющиеся за раздувание мира, и объяснить, что произошло подробно. Это то, что я делаю at the moment.
  • попробуйте выделить еще несколько раз, прежде чем называть его завершением работы и сбой всей программы. Это похоже на хорошую стратегию, но больше похоже на скрещивание пальцев, поскольку программа все еще может потерпеть крах из-за поведения структуры (а не программы). Этот подход может быть просто параноидальным и неправильным, поскольку неудавшееся распределение не обязательно даст вам возможность «перераспределить» и просто выключит программу.
  • изобретает какой-то механизм стресс-теста, чтобы дать уверенность владельцу структуры, что он может справиться с максимальной способностью, которую пользователь ожидает (что может быть тяжело, потому что эти стресс-тесты могут быть очень обманчивыми, поскольку память может быть доступна теперь, но в более интенсивное время памяти, это может не быть).

Существует также смешная возможность не иметь достаточного количества памяти, чтобы на самом деле уловить исключение. Эта программа, похоже, не поймает ее (что может или не может быть связано с наличием достаточного количества памяти).

#include <stdint.h> 
#include <exception> 
#include <iostream> 
#include <stdlib.h> 

int main(int argc, char **argv) 
{ 
    uint_fast32_t leaked_bytes = 0; 

    while (1) {  
     try { 
      new uint8_t; 
     } catch (const std::bad_alloc& e) { 
      std::cout << "successfully leaked" << leaked_bytes << " bytes." << '\n'; 
      exit(0); 
     } catch (const std::exception& e) { 
      std::cout << "I caught an exception, but not sure what it was...\n"; 
      exit(0); 
     } 

     ++leaked_bytes;  
    } 
} 

Эта программа действительно позволяет мне обрабатывать отказ, прежде чем программа завершается, хотя:

#include <stdlib.h> 
#include <stdint.h> 
#include <stdio.h> 

int main(int argc, char **argv) 
{ 
    uint_fast32_t bytes_leaked = 0; 

    while (1) { 
     if (malloc(1) == 0) { 
      printf("leaked %u bytes.\n", bytes_leaked); 
      exit(0); 
     }   
     ++bytes_leaked; 
    } 

    return 0; 
} 

nothrow работает правильно, а также:

#include <stdio.h> 
#include <stdint.h> 
#include <stdlib.h> 
#include <new> 

int main(int argc, char **argv) 
{ 
    uint64_t leaked_bytes = 0; 

    while (1) {   
     uint8_t *byte = new (std::nothrow) uint8_t; 
     if (byte == nullptr) { 
      printf("leaked %llu bytes.\n", leaked_bytes); 
      exit(0); 
     }   
     ++leaked_bytes; 
    } 

    return 0; 
} 

EDIT:

Я думаю, Я нашел решение этой проблемы. Существует неотъемлемая наивность при хранении динамических структур данных в основном процессе. То есть, эти структуры не гарантируют успеха и могут сломаться в любой момент.

Лучше всего создавать все динамические структуры в другом процессе и иметь некоторую стратегию перезапуска в случае неожиданной проблемы.

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

конец EDIT.

Есть ли способ управления проблемами распределения в таких динамических классах, повысить осведомленность моего пользователя об этих рисках?

+1

Насколько я знаю, std :: vector не имеет конструктора пропускной способности. Он имеет конструктор размеров, который фактически выделяет объекты, а не просто резервирует память. –

+13

Для меня бросать 'std :: bad_alloc' - самый идиоматический способ сообщить о проблеме« из памяти ». В конце концов, ваш конструктор не может выполнять свою работу, если он не может получить память. –

+1

Помните, что это не просто 'std :: vector', который может закончиться без памяти. Таким образом, можно использовать 'std :: string' или любой другой класс, используя память. Но большинство 64-битных систем не исчерпываются. Вместо этого память переключается на диск, и программа перемалывается до виртуальной остановки. – MSalters

ответ

0

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

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

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

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

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

Ответ: По сути, невозможно обеспечить простую пару интерфейса/реализации класса, который управляет динамической памятью. У вас либо есть простой интерфейс/реализация, который приведет к вашей программе, такой как std :: vector; или сложный интерфейс/реализация, для которой требуется одно из следующего или что-то более умное:

  1. Программист должен предоставить уже имеющиеся блоки памяти. Поскольку память уже существует, и вы знаете, сколько ее у вас есть, вы можете просто не выделять больше, чем можете себе позволить.
  2. Структура данных использует свою собственную схему управления памятью, используя отдельный процесс, который будет разбиваться, а не процесс, в котором размещена структура.
  3. Структура данных управляется отдельным процессом в целом, подобно серверам баз данных. Это упрощает перезапуск процесса в случае его отказа.
+3

«Бросание исключений вызывает сомнения». Если вы не доверяете своему языку и его разработчикам стандартных библиотек, возможно, вы выбрали неправильный язык. –

+0

Вы можете бросить и поймать bad_alloc без выделения памяти. Таким образом, нет никаких проблем с броском. – rici

+0

Я проверю, если попытка проверки утечки снова после проверки некоторых флагов компилятора позволит мне поймать исключение. На данный момент это говорит о том, что «terminate called recursively» Это приложение попросило Runtime прекратить его необычным способом. Для получения дополнительной информации обратитесь в службу поддержки приложения. . " EDIT: g ++ leak.cpp -o leak -O3 -s -DNDEBUG ничего не изменил. – Dmitry

Смежные вопросы