Я хочу открыть файл в конструкторе класса. Возможно, открытие может потерпеть неудачу, тогда строительство объекта не может быть завершено. Как справиться с этим сбоем? Выбросить исключение? Если это возможно, как обрабатывать его в конструкторе без броска?
Как обрабатывать сбой в конструкторе на С++?
Ответ 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
. Тот, который может вернуть недопустимый объект returnoptional<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.
-
Более опасным может быть закрытие файла в деструкторе, если это сбрасывает буфер записи. Ни в коем случае не нужно обрабатывать какие-либо ошибки, которые могут произойти в этой точке должным образом.
Вы можете справиться с этим без исключения, оставив объект в состоянии "сбой". Так вы должны это делать в случаях, когда бросать запрещается, но, конечно, ваш код должен проверить наличие ошибки.