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

В С++ 11 представлены конструкторы исключений, принимающие `const char *`. Но почему?

Стандартный дефект библиотеки # 254, который охватывает добавление новых конструкторов исключений:

std::logic_error::logic_error(const char* what_arg);
std::runtime_error::runtime_error(const char* what_arg);
// etc.

дает в качестве обоснования идею о том, что сохранение std::string открывает некоторые банки червей, связанные с потенциально проблемным распределением памяти.

Тем не менее, после начала обсуждения orlp в Lounge, мне кажется, что если бы стандарт не должен был утверждать, что what_arg были только строковым литералом (или указатель на какой-то другой буфер статической продолжительности хранения), в любом случае ему придется выполнить копию C-строки, чтобы поддерживать четкость функции-члена what().

Это потому, что:

void bar() {
   char buf[] = "lol";
   throw std::runtime_error(buf);
}

void foo() {
   try {
      bar();
   }
   catch (std::exception& e) {
      std::cout << e.what() << '\n';   // e.what() points to destroyed data!
   }
}

Но я не вижу такого мандата. На самом деле, если объекты исключительных объектов имеют глубокую копию what_arg или нет, они полностью не определены.

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

Является ли это потенциально стандартным дефектом, или я здесь что-то не хватает?
Это всего лишь случай "программиста: не пропускайте оборванных указателей в любом месте"?

4b9b3361

Ответ 1

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

Например, допустим, что компилятор объединяет все строковые литералы вместе в диапазон, ограниченный __string_literals_begin и __string_literals_end. Затем где-то внутри конструктора для std::exception он может иметь код в общем порядке:

namespace std {
    exception::exception(char const *s) { 
        if (in_range(s, __string_literals_begin, __string_literals_end)) {
            stored_what = s;
            destroy_stored_what = false;
        }
        else {
            stored_what = dupe(s);
            destroy_stored_what = true;
        }
        // ...
    }

    exception::~exception() {
        if (destroy_stored_what)
            delete_string(stored_what);
}

Последний комментарий в связанном DR:

[Оксфорд: Предлагаемая резолюция просто решает проблему построения объектов исключения с помощью const char * и строковых литералов без необходимости явного включения или построения std::string. ]

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

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

Ответ 2

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

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

Ответ 3

Разрешение дефекта решает другую проблему. Нижняя часть дефекта имеет следующее примечание:

[Оксфорд: Предлагаемая резолюция просто решает проблему построения объектов исключения с помощью const char * и строковых литералов без необходимости явного включения или построения std::string. ]

Ответ 4

Как libС++ справляется с этой проблемой?

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

class _LIBCPP_EXCEPTION_ABI logic_error
    : public exception
{
private:
    _VSTD::__libcpp_refstring __imp_;

Где __imp_ инициализируется следующим образом:

logic_error::logic_error(const string& msg) : __imp_(msg.c_str()) {}

logic_error::logic_error(const char* msg) : __imp_(msg) {}

Эта строка, __libcpp_refstring, выделяет новый буфер при сохранении нового char const*:

explicit __libcpp_refstring(const char* msg) {
        std::size_t len = strlen(msg);
        _Rep_base* rep =
             static_cast<_Rep_base *>(::operator new(sizeof(*rep) + len + 1));

Но, конечно, конструктор копирования __libcpp_refstring не выделяет новый буфер.

(Да, это не отвечает на вопрос, но он должен пролить свет на вопрос, который я думаю. Например, если в logic_error было только std::string const& ctor, то должно было быть одно дополнительное распределение для рефректора.)