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

В более эффективном С++ Скотт Мейерс говорит

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

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

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

Ответ 1

Раздел 18.8.1 [exception]/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 также указывает noexcept copy:

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;
  };
}

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

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

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

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

Ответ 2

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

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) или косвенно вызван вызовами функций в Стандартная библиотека С++. [Примечание: в частности, глобальное распределение функция не вызывается для выделения хранилища для [...] для объекта исключения (15.1). -end note]

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

Ответ 3

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

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