Ответ 1
Если конструкция объекта завершается неудачно, выведите исключение.
Альтернатива ужасная. Вам нужно будет создать флаг, если конструкция будет выполнена, и проверите ее в каждом методе.
Я хочу открыть файл в конструкторе класса. Возможно, открытие может потерпеть неудачу, тогда строительство объекта не может быть завершено. Как справиться с этим сбоем? Выбросить исключение? Если это возможно, как обрабатывать его в конструкторе без броска?
Если конструкция объекта завершается неудачно, выведите исключение.
Альтернатива ужасная. Вам нужно будет создать флаг, если конструкция будет выполнена, и проверите ее в каждом методе.
Я хочу открыть файл в конструкторе класса. Возможно, открытие может потерпеть неудачу, тогда строительство объекта не может быть завершено. Как справиться с этим сбоем? Выбросить исключение?
Да.
Если это возможно, как обрабатывать его в конструкторе без броска?
Ваши варианты:
if (X x) ...
(т.е. объект может быть оценен в булевом контексте, обычно путем предоставления operator bool() const
или аналогичного интегрального преобразования), но то у вас нет x
в области запроса для получения подробной информации об ошибке. Это может быть известно из, например, if (std::ifstream f(filename)) { ... } else ...;
bool worked; X x(&worked); if (worked) ...
if (X* p = x_factory()) ...
</li>
<li>
X x;//никогда нельзя использовать; if (init_x (& x))... `Короче говоря, С++ предназначен для предоставления элегантных решений таких проблем: в этом случае исключения. Если вы искусственно ограничиваете себя от их использования, то не ожидайте, что там будет что-то еще, что делает половину хорошей работы.
(PS Мне нравится передавать переменные, которые будут изменены указателем - как указано выше worked
). Я знаю, что часто задаваемые вопросы часто отклоняют его, но не согласны с рассуждениями. Не особенно интересно обсуждать это, если у вас нет чего-то, что не покрывается FAQ.)
Мое предложение для этой конкретной ситуации состоит в том, что если вы не хотите, чтобы constuctor терпит неудачу, потому что, если он не может открыть файл, то избегайте этой ситуации. Передайте в уже открытый файл конструктору, если это то, что вы хотите, тогда оно не может потерпеть неудачу...
Новый стандарт С++ переопределяет это по-разному, чтобы вернуться к этому вопросу.
Лучшие варианты:
Именованный необязательный: иметь минимальный частный конструктор и именованный конструктор: 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;
});
}
Я хочу открыть файл в конструкторе класса.
Почти наверняка плохая идея. Очень мало случаев при открытии файла во время строительства.
<я > Возможно, открытие может потерпеть неудачу, тогда строительство объекта не может быть завершено. Как справиться с этим сбоем? Выбросить исключение?
Да, это будет так.
<я > Если это возможно, как обрабатывать его в конструкторе без броска?
Сделайте возможным, чтобы полностью сконструированный объект вашего класса мог быть недействительным. Это означает предоставление подпрограмм проверки, их использование и т.д.... ick
Один из способов - исключить исключение. Другим является наличие функции 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 ( );
Конструктор может хорошо открыть файл (не обязательно плохую идею) и может бросать, если сбой файла файла не выполняется, или если входной файл не содержит совместимых данных.
Разумно поведение конструктора для исключения исключений, однако вы будете ограничены в отношении его использования.
Вы не сможете создавать экземпляры статического (компиляционного уровня файла) этого класса, которые создаются до "main()", поскольку конструктор должен быть когда-либо задан только в регулярном потоке.
Это может распространяться на более позднюю "первую" ленивую оценку, когда что-то загружается при первой необходимости, например, в конструкции boost:: once функция call_once никогда не должна бросаться.
Вы можете использовать его в среде IOC (инверсия управления/зависимостей). Вот почему среда IOC выгодна.
Будьте уверены, что если ваш конструктор выбрасывает, ваш деструктор не будет вызван. Итак, все, что вы инициализировали в конструкторе до этой точки, должно содержаться в объекте RAII.
Более опасным может быть закрытие файла в деструкторе, если это сбрасывает буфер записи. Ни в коем случае не нужно обрабатывать какие-либо ошибки, которые могут произойти в этой точке должным образом.
Вы можете справиться с этим без исключения, оставив объект в состоянии "сбой". Так вы должны это делать в случаях, когда бросать запрещается, но, конечно, ваш код должен проверить наличие ошибки.