Подтвердить что ты не робот

(не) с использованием std::string в исключениях

Я всегда читаю, что мне не следует бросать std::string или некоторые другие классы, выделяющие память. например здесь или, что более важно, здесь в пункте 3. - Дон ' t вставить объект std::string.

Итак, теперь я пытаюсь вставить boost:: exception в мой проект и что я вижу: много строк.

Почему не повышается соответствие его собственной рекомендации?

И если у меня есть параметры, которые не могут быть жестко закодированы, например, безопасно в конфигурационном файле, как я могу помещать их в исключение, не используя std::string?

Или в руководстве не использовать std::string только использовать std::string как можно реже. Я немного смущен...

Я провел некоторое исследование. Пожалуйста, поправьте меня, если я ошибаюсь.


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

Я пробовал это:

struct xexception {
  int *ttt[10];
  xexception() {
    ttt[0] = new int[0xfffffffL];
    ttt[1] = new int[0xfffffffL];
    ttt[2] = new int[0xfffffffL];
    ttt[3] = new int[0xfffffffL];
    ttt[4] = new int[0xfffffffL];
    ttt[5] = new int[0xfffffffL];
    ttt[6] = new int[0xfffffffL];
    ttt[7] = new int[0xfffffffL];
    ttt[8] = new int[0xfffffffL];
    ttt[9] = new int[0xfffffffL];
  }

  ~xexception() throw() {
    //never happen
    delete[] ttt[0];
    delete[] ttt[1];
    delete[] ttt[2];
    delete[] ttt[3];
    delete[] ttt[4];
    delete[] ttt[5];
    delete[] ttt[6];
    delete[] ttt[7];
    delete[] ttt[8];
    delete[] ttt[9];
  }
};

int main(int argc, const char *argv[]) {
  try {
    throw(xexception());
  }
  catch (const xexception &e) {
    std::cerr << "\nttt " << e.ttt[0][0] << std::endl;
  }
  catch (std::bad_alloc) {
    std::cerr << "bad alloc" << std::endl;
  }

  return 0;
}

В результате получается bad_alloc и огромная утечка памяти.

Теперь, если я делаю выделение раньше, он также выдает bad_alloc, но до создания исключения.


Мое исключение из концепции исключений:

Кому это нужно? Если у меня есть bad_alloc в моей программе, из-за memory_leak или что-то еще (я говорю о программах на ПК, а не о микроконтроллерах), у меня есть другие проблемы. Может быть, я могу понять, что произошел bad_alloc, но где? На моем выделении во время функции (одна из, возможно, 1000) или в std::string (ну, я знаю, это строка, но... нет возможности манипулировать памятью строки... или ее рассеять).

try {
  // where is the error???
  int *x = new int[100];  // here?
  ....
  int *y = new int[100];  // or here?
  ....
  int *z = new int[100];
  ....
  int *w = new int[100];
  ....
  int *t = new int[100];
  ....
  int *f = new int[100];

  ....

  std::string str("asdfasdfasdfasdfasdfasdfasdf"); // maybe here
}
catch (the error) {
  ....
}

А потом? Должен ли я попытаться выяснить, где это происходит? Поэтому я бы использовал valgrind не исключение.

void foo() {
  int *i = new int[1];
  foo();
}

try {
  foo();
}
chatch( bad_boy ) {
  go_exception_handler_go(parameters); // oh, shit happens: also an stack_overflow may happend, cause stack is also full
}

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

Пожалуйста, не поймите меня неправильно. Поскольку я видел boost:: exception, я переписал свой класс исключений (до ожидания ответа), но я также думаю, что на самом деле нет необходимости собирать каждый песок.

4b9b3361

Ответ 1

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

Теперь, если (или) из задействованных исключений просто вызовет terminate() anyways (как и по умолчанию для неперехваченного исключения), вам действительно не нужно беспокоиться об этом. Например, если ваше приложение не может обрабатывать bad_alloc (невозможно восстановить из-за-памяти), вам не нужно беспокоиться о конструкторах копирования (например, std::string), которые могут его выбрасывать.

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

С++ 11 сделать это намного проще, если возможно, с помощью конструкторов перемещения (вместо конструкторов копирования). Поскольку конструктор перемещения для std::string никогда не генерирует исключения, вы можете безопасно использовать std:string в своем типе исключения, если вы правильно реализуете конструкторы перемещения и убедитесь, что они используются. Обратите внимание, что начальная конструкция объекта, который должна быть выбрана в выражении throw, НЕ является частью процесса бросания исключений, поэтому конструктор может генерировать исключение, не вызывая двойного исключения (и terminate()). Поэтому, если у вас есть:

throw some_function();

some_function может генерировать исключение (например, bad_alloc), не возвращая объект, который нужно выбросить, и это прекрасно. Если он не генерирует исключение (и возвращает допустимый объект), конструктор перемещения для типа исключения будет использоваться (если он доступен) для процесса броска исключения, и этот конструктор перемещения не должен генерировать исключение.


Полностью независимый от вышесказанного, всякий раз, когда вы вызываете new, вам нужно убедиться, что ровно одно место вызовет delete в каждом возможном случае, или вы будете утечка памяти (или сбой при двойном удалении). Это становится сложным в любой момент, когда у вас есть функция, которая вызывает new, а затем делает что-то еще, что может вызвать исключение (например, вызов new снова). Если это происходит в конструкторе, деструктор для объекта не будет вызываться (хотя деструкторы для базовых классов и полей будут), поэтому вы не можете выполнить очистку в деструкторе, как вы пытаетесь сделать с вашим примером.

К счастью, std::unique_ptr существует, чтобы сделать это намного проще. Если вы напишете свой класс исключений как:

struct xexception {
  std::unique_ptr<int[]> ttt[10];
  xexception() {
    ttt[0].reset(new int[0xfffffffL]);
    ttt[1].reset(new int[0xfffffffL]);
    ttt[2].reset(new int[0xfffffffL]);
    ttt[3].reset(new int[0xfffffffL]);
    ttt[4].reset(new int[0xfffffffL]);
    ttt[5].reset(new int[0xfffffffL]);
    ttt[6].reset(new int[0xfffffffL]);
    ttt[7].reset(new int[0xfffffffL]);
    ttt[8].reset(new int[0xfffffffL]);
    ttt[9].reset(new int[0xfffffffL]);
  }
};

он должен работать, а не утечка памяти.

Ответ 2

Хотя я думаю, что не использовать std::string для ядра, фундаментальные исключения могут быть хорошим ориентиром, я не думаю, что пользовательские библиотеки/приложения должны обязательно следовать этому.

Могут быть и другие причины, но вы нажимаете на основной: вы хотите указать контекстно-значимую информацию пользователя (или разработчика), которую вы часто не можете делать с простой литеральной строкой. Для этого требуется динамическое распределение. Если по какой-то причине у вас был bad_alloc, вы, вероятно, уже были заняты, чтобы начать с него, поэтому он не покупает и не теряет вас.

Edit:

Кстати: деструктор std::exception помечен как виртуальный по какой-либо причине!