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

Как обрабатывать сбой в конструкторе на С++?

Я хочу открыть файл в конструкторе класса. Возможно, открытие может потерпеть неудачу, тогда строительство объекта не может быть завершено. Как справиться с этим сбоем? Выбросить исключение? Если это возможно, как обрабатывать его в конструкторе без броска?

4b9b3361

Ответ 1

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

Альтернатива ужасная. Вам нужно будет создать флаг, если конструкция будет выполнена, и проверите ее в каждом методе.

Ответ 2

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

Да.

Если это возможно, как обрабатывать его в конструкторе без броска?

Ваши варианты:

  • перепроектировать приложение, чтобы он не нуждался в конструкторах, чтобы он не метался - действительно, сделайте это, если возможно
  • добавить флаг и проверить успешное построение
    • у вас может быть каждая функция-член, которая может быть законно вызвана сразу после того, как конструктор проверяет флаг, в идеале бросая, если он установлен, но в противном случае возвращает код ошибки
      • Это некрасиво и сложно поддерживать право, если у вас есть неустойчивая группа разработчиков, работающих над кодом.
      • Вы можете получить некоторую проверку времени компиляции, если объект полиморфно отнесется к любой из двух реализаций: успешно построенной и всегда ошибки, но это приводит к использованию кучи и производительности.
    • Вы можете перенести бремя проверки флага из вызываемого кода на вызываемого абонента, задокументировав требование, чтобы они вызывали некоторую "is_valid()" или аналогичную функцию перед использованием объекта: снова подвержен ошибкам и уродливым, но еще более распространенным, неприемлемым и неконтролируемым.
      • Вы можете сделать это немного проще и более локализованным для вызывающего, если вы поддерживаете что-то вроде: if (X x) ... (т.е. объект может быть оценен в булевом контексте, обычно путем предоставления operator bool() const или аналогичного интегрального преобразования), но то у вас нет x в области запроса для получения подробной информации об ошибке. Это может быть известно из, например, if (std::ifstream f(filename)) { ... } else ...;
  • у вызывающего есть поток, за который они отвечают за открытие... (известное как Injection Dependency или DI)... в некоторых случаях это не так хорошо работает:
    • У вас все еще могут быть ошибки при использовании потока внутри вашего конструктора, что тогда?
    • сам файл может быть деталью реализации, которая должна быть закрыта для вашего класса, а не подвергаться вызову: что, если вы хотите удалить это требование позже? Например: вы, возможно, читали таблицу поиска предварительно рассчитанных результатов из файла, но делали ваши вычисления настолько быстрыми, что нет необходимости в предварительном вычислении - это болезненно (иногда даже нецелесообразно в корпоративной среде), чтобы удалить файл во всех точках использование клиента, а также гораздо больше перекомпиляции, чем потенциально просто перетягивание.
  • заставить вызывающего пользователя предоставить буфер для переменной успеха/отказа/ошибки-условия, которую устанавливает конструктор: например. bool worked; X x(&worked); if (worked) ...
    • это бремя и многословие привлекает внимание и, как мы надеемся, делает вызывающего гораздо более осознанным необходимость консультироваться с переменной после построения объекта
  • заставить вызывающего объекта построить объект через какую-либо другую функцию, которая может использовать коды возврата и/или исключения:
    • if (X* p = x_factory()) ...
    • Smart_Ptr_Throws_On_Null_Deref p_x = x_factory(); </li> <li> X x;//никогда нельзя использовать; if (init_x (& x))... `
    • и т.д...

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

(PS Мне нравится передавать переменные, которые будут изменены указателем - как указано выше worked). Я знаю, что часто задаваемые вопросы часто отклоняют его, но не согласны с рассуждениями. Не особенно интересно обсуждать это, если у вас нет чего-то, что не покрывается FAQ.)

Ответ 3

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

Ответ 4

Новый стандарт С++ переопределяет это по-разному, чтобы вернуться к этому вопросу.

Лучшие варианты:

  • Именованный необязательный: иметь минимальный частный конструктор и именованный конструктор: static std::experimental::optional<T> construct(...). Последний пытается настроить поля-члены, обеспечивает инвариантность и вызывает только частный конструктор, если он, безусловно, преуспеет. Частный конструктор заполняет только поля элемента. Легко протестировать опционально и недорого (даже копия может быть сохранена в хорошей реализации).

  • Функциональный стиль: хорошая новость заключается в том, что (неименованные) конструкторы никогда не являются виртуальными. Поэтому вы можете заменить их на статическую функцию-член шаблона, которая, помимо параметров конструктора, принимает два (или более) лямбда: один, если он был успешным, один, если он не удался. "Реальный" конструктор по-прежнему является частным и не может не работать. Это может показаться излишним, но lambdas прекрасно оптимизируются компиляторами. Возможно, вы даже избавитесь от if необязательного этого способа.

Хороший выбор:

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

  • Класс Builder: если конструкция сложна, у вас есть класс, который выполняет валидацию и, возможно, некоторую предварительную обработку до такой степени, что операция не может потерпеть неудачу. Пусть у него есть способ вернуть статус (yep, функция ошибки). Я лично сделал бы его только для стека, поэтому люди не пройдут мимо него; то пусть он имеет метод .build(), который создает другой класс. Если конструктор является другом, конструктор может быть закрытым. Он может даже взять что-то, что может построить только строитель, чтобы он задокументировал, что этот конструктор должен вызываться только строителем.

Плохие выборы: (но видели много раз)

  • Флаг: не испортите свой инвариант класса, имея "недопустимое" состояние. Именно поэтому мы имеем optional<>. Подумайте о optional<T>, который может быть недействительным, T, который не может. Функция (член или глобальная), работающая только на действительных объектах, работает на T. Тот, который уверенно возвращает действительные работы на T. Тот, который может вернуть недопустимый объект return optional<T>. Тот, который может аннулировать объект, принимает неконстантный optional<T>& или optional<T>*. Таким образом, вам не нужно будет проверять каждую функцию, которая действительна для вашего объекта (и те, что if могут стать немного дорогими), но затем также не прерывать конструктор.

  • Конструкция и сеттеры по умолчанию: это в основном то же самое, что и флаг, только на этот раз вы вынуждены иметь изменяемый шаблон. Забудьте о сеттерах, они излишне усложняют ваш инвариант класса. Помните, что ваш класс прост, а не простой.

  • Конструкция по умолчанию и init(), которая принимает параметры ctor: это ничего лучше, чем функция, возвращающая optional<>, но требует двух конструкций и помешает вашему инварианту.

  • Возьмите bool& succeed: Это было то, что мы делали до optional<>. Причина optional<> выше, вы не можете ошибочно (или небрежно!) Игнорировать флаг succeed и продолжать использовать частично сконструированный объект.

  • Factory, который возвращает указатель: это менее общий, поскольку он заставляет объект динамически выделяться. Либо вы возвращаете заданный тип управляемого указателя (и, следовательно, ограничиваете схему распределения/масштабирования), либо возвращаете обнаженных ptr и клиентов риска. Кроме того, с переходом схемы по производительности это может стать менее желательным (локальные жители, когда они хранятся в стеке, очень быстры и удобны для кеширования).

Пример:

#include <iostream>
#include <experimental/optional>
#include <cmath>

class C
{
public:
    friend std::ostream& operator<<(std::ostream& os, const C& c)
    {
        return os << c.m_d << " " << c.m_sqrtd;
    }

    static std::experimental::optional<C> construct(const double d)
    {
        if (d>=0)
            return C(d, sqrt(d));

        return std::experimental::nullopt;
    }

    template<typename Success, typename Failed>
    static auto if_construct(const double d, Success success, Failed failed = []{})
    {
        return d>=0? success( C(d, sqrt(d)) ): failed();
    }

    /*C(const double d)
    : m_d(d), m_sqrtd(d>=0? sqrt(d): throw std::logic_error("C: Negative d"))
    {
    }*/
private:
    C(const double d, const double sqrtd)
    : m_d(d), m_sqrtd(sqrtd)
    {
    }

    double m_d;
    double m_sqrtd;
};

int main()
{
    const double d = 2.0; // -1.0

    // method 1. Named optional
    if (auto&& COpt = C::construct(d))
    {
        C& c = *COpt;
        std::cout << c << std::endl;
    }
    else
    {
        std::cout << "Error in 1." << std::endl;
    }

    // method 2. Functional style
    C::if_construct(d, [&](C c)
    {
        std::cout << c << std::endl;
    },
    []
    {
        std::cout << "Error in 2." << std::endl;
    });
}

Ответ 5

Я хочу открыть файл в конструкторе класса.

Почти наверняка плохая идея. Очень мало случаев при открытии файла во время строительства.

<я >  Возможно, открытие может потерпеть неудачу, тогда строительство объекта не может быть завершено. Как справиться с этим сбоем? Выбросить исключение?

Да, это будет так.

<я >  Если это возможно, как обрабатывать его в конструкторе без броска?

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

Ответ 6

Один из способов - исключить исключение. Другим является наличие функции bool is_open() или bool is_valid(), которая возвращает false, если что-то пошло не так в конструкторе.

В некоторых комментариях говорится, что неправильно открыть файл в конструкторе. Я укажу, что ifstream является частью стандарта С++, он имеет следующий конструктор:

explicit ifstream ( const char * filename, ios_base::openmode mode = ios_base::in );

Он не генерирует исключение, но имеет функцию is_open:

bool is_open ( );

Ответ 7

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

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

  • Вы не сможете создавать экземпляры статического (компиляционного уровня файла) этого класса, которые создаются до "main()", поскольку конструктор должен быть когда-либо задан только в регулярном потоке.

  • Это может распространяться на более позднюю "первую" ленивую оценку, когда что-то загружается при первой необходимости, например, в конструкции boost:: once функция call_once никогда не должна бросаться.

  • Вы можете использовать его в среде IOC (инверсия управления/зависимостей). Вот почему среда IOC выгодна.

  • Будьте уверены, что если ваш конструктор выбрасывает, ваш деструктор не будет вызван. Итак, все, что вы инициализировали в конструкторе до этой точки, должно содержаться в объекте RAII.

  • Более опасным может быть закрытие файла в деструкторе, если это сбрасывает буфер записи. Ни в коем случае не нужно обрабатывать какие-либо ошибки, которые могут произойти в этой точке должным образом.

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