Должен ли я объявить конструктор копирования моих исключений noexcept?

В статье More Effective C++ Скотт Мейерс говорит:

C++ указывает, что объект, созданный как исключение, копируется.

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

Тем не менее я был удивлен, увидев, что стандартная реализация библиотеки, поставляемая с GCC 4.7.1, не определяет эти конструкторы копирования для std::bad_alloc и std::exception. Разве они не должны дать им определение noexcept?


person qdii    schedule 08.02.2014    source источник


Ответы (3)


Раздел 18.8.1 [исключение]/p1 определяет:

namespace std {
    class exception {
    public:
      exception() noexcept;
      exception(const exception&) noexcept;
      exception& operator=(const exception&) noexcept;
      virtual ~exception();
      virtual const char* what() const noexcept;
  };
}

т.е. конструктор копирования и назначение копии std::exception должны быть noexcept, и это можно проверить с помощью:

static_assert(std::is_nothrow_copy_constructible<std::exception>::value, "");
static_assert(std::is_nothrow_copy_assignable<std::exception>::value, "");

т.е. если реализация не делает эти элементы исключенными, то она не соответствует требованиям в этом отношении.

Точно так же 18.6.2.1 [bad.alloc]/p1 также не указывает кроме копии:

namespace std {
       class bad_alloc : public exception {
       public:
         bad_alloc() noexcept;
         bad_alloc(const bad_alloc&) noexcept;
         bad_alloc& operator=(const bad_alloc&) noexcept;
         virtual const char* what() const noexcept;
  };
}

Кроме того, все типы исключений, определенные в стандартном наборе, не содержат членов-копий, явно или неявно. Для типов, определенных в <stdexcept>, это обычно реализуется с помощью буфера с подсчетом ссылок для строки what(). Это ясно указано в [исключение]/p2:

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

То есть в качественной реализации (а для создания качественной реализации в этом плане не требуется геройства) мало того, что экземпляры-члены типов исключений не будут генерировать исключение (естественно потому, что они помечены noexcept), они еще и не будут позвони terminate().

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

person Howard Hinnant    schedule 08.02.2014
comment
Итак, правильно ли я понимаю, что фраза не завершается с исключением, позволяет плохой реализации вызывать std::terminate (т.е. в теле вызывать исключение и в результате noexcept вызывать завершение)? - person Johannes Schaub - litb; 08.02.2014
comment
@JohannesSchaub-litb: Недавно я прочитал ответ на другой вопрос SO, в котором предлагалось, чтобы конструктор std::chrono::seconds мог вызывать this_thread::sleep. Итак, в духе настолько нелепой реализации, что ее время на рынке измерялось бы миллисекундами, да, конечно. Стандарт не является идеальным документом и никогда не защитит от такой нелепости на 100%. Я надеюсь, что он даже не попытается. Есть так много гораздо более важных вещей, которые ему нужно сделать. - person Howard Hinnant; 08.02.2014
comment
Однако это не отвечает на вопрос, не так ли? ОП поместил мои исключения в заголовок, что означает определяемые пользователем классы исключений. Вы говорите только о стандартных классах исключений. Возможно, вам просто нужно добавить немного увещевания следовать примеру Стандарта. - person Spencer; 02.02.2021

Выделение памяти для исключений производится вне обычных каналов:

15.1 Создание исключения [except.throw]

3 Вызов исключения копией-инициализирует (8.5, 12.8) временный объект, называемый объектом исключения. [...]

4 Память для объекта исключения выделяется неуказанным образом, за исключением случаев, указанных в 3.7.4.1. [...]

3.7.4.1 Функции распределения [basic.stc.dynamic.allocation]

4 Функция глобального распределения вызывается только в результате нового выражения (5.3.4), либо вызывается напрямую с использованием синтаксиса вызова функции (5.2.2), либо вызывается косвенно через вызовы функций из стандартной библиотеки C++. [Примечание: в частности, функция глобального распределения не вызывается для выделения памяти для [...] для объекта исключения (15.1). — конец примечания]

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

person TemplateRex    schedule 08.02.2014

Что ж, объявить его noexcept — это нормально, но для этого требуется, чтобы вы могли ГАРАНТИРОВАТЬ, что он не будет генерировать исключение (и для переносимого кода во всех его реализациях!). Я ожидаю, что это причина того, что стандартные не объявлены таким образом.

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

person Mats Petersson    schedule 08.02.2014
comment
Разве стандарт не должен гарантировать, что его конструктор копирования исключений не будет генерировать исключение? - person qdii; 08.02.2014
comment
Это может быть невозможно. - person Mats Petersson; 08.02.2014