Бросить исключение из инициализатора конструктора
Каков наилучший способ вызвать исключение из инициализатора конструктора?
Например:
class C {
T0 t0; // can be either valid or invalid, but does not throw directly
T1 t1; // heavy object, do not construct if t0 is invalid, by throwing before
C(int n)
: t0(n), // throw exception if t0(n) is not valid
t1() {}
};
Я думал, может быть, делает обертку, например t0(throw_if_invalid(n))
,
Какова практика обработки таких случаев?
4 ответа
Я думаю, что есть несколько способов сделать это. Из того, что я понимаю, n
может принимать только определенный диапазон чисел. Для этого вы можете запретить запуск конструктора:
template <typename T, T Min, T Max>
class ranged_type_c
{
public:
typedef T value_type;
ranged_type_c(const value_type& pX) :
mX(pX)
{
check_value();
}
const value_type& get(void) const
{
return mX;
}
operator const value_type&(void) const
{
return get();
}
// non-const overloads would probably require a proxy
// of some sort, to ensure values remain valid
private:
void check_value(void)
{
if (mX < Min || mX > Max)
throw std::range_error("ranged value out of range");
}
value_type mX;
};
Может быть более конкретным, но это идея. Теперь вы можете зажать диапазон:
struct foo_c
{
foo_c(ranged_value_c<int, 0, 100> i) :
x(i)
{}
int x;
};
Если вы передадите значение, которое не лежит в диапазоне от 0 до 100, вышеприведенное выкинет
Во время выполнения, я думаю, ваша оригинальная идея была лучшей:
template <typename T>
const T& check_range(const T& pX, const T& pMin, const T& pMax)
{
if (pX < pMin || pX > pMax)
throw std::range_error("ranged value out of range");
return pValue;
}
struct foo
{
foo(int i) :
x(check_range(i, 0, 100))
{}
int x;
}
И это все. То же, что и выше, но 0 и 100 можно заменить вызовом некоторой функции, которая возвращает действительный минимум и максимум.
Если вы в конечном итоге используете вызов функции для получения допустимых диапазонов (рекомендуется, чтобы помеха была минимальной, а организация выше), я бы добавил перегрузку:
template <typename T>
const T& check_range(const T& pX, const std::pair<T, T>& pRange)
{
return check_range(pX, pRange.first, pRange.second); // unpack
}
Чтобы разрешить такие вещи:
std::pair<int, int> get_range(void)
{
// replace with some calculation
return std::make_pair(0, 100);
}
struct foo
{
foo(int i) :
x(check_range(i, get_range()))
{}
int x;
}
Если бы я выбрал, я бы выбрал методы времени выполнения, даже если диапазон был во время компиляции. Даже при низкой оптимизации компилятор сгенерирует тот же код, и он будет гораздо менее неуклюжим и, возможно, более чистым для чтения, чем классовая версия.
Вы можете throw
из выражений, которые инициализируют t0
или же t1
или любой конструктор, который принимает хотя бы один аргумент.
class C {
T0 t0; // can be either valid or invalid, but does not throw directly
T1 t1; // heavy object, do not construct if t0 is invalid, by throwing before
C(int n) // try one of these alternatives:
: t0( n_valid( n )? n : throw my_exc() ), // sanity pre-check
OR t1( t0.check()? throw my_exc() : 0 ), // add dummy argument to t1::t1()
OR t1( t0.check()? throw my_exc() : t1() ) // throw or invoke copy/move ctor
{}
};
Обратите внимание, что throw
выражение имеет void
тип, изготовление throw
больше похоже на оператор, чем на утверждение. ?:
Оператор имеет особый случай, чтобы предотвратить void
от вмешательства в его тип удержания.
Это способ выкинуть из списка инициализатора
C(int n)
: t0(n > 0 ? n : throw std::runtime_error("barf")),
t1() {}
Вы говорите "выбросить исключение, если t0(n) недопустимо". Почему бы вам не выбросить из конструктора T0?
Объект должен быть действительным после строительства.
Просто оберните класс T0 внутри другого класса, который выбрасывает в таких случаях:
class ThrowingT0
{
T0 t0;
public:
explicit ThrowingT0(int n) : t0(n) {
if (t0.SomeFailureMode())
throw std::runtime_error("WTF happened.");
};
const T0& GetReference() const {
return t0;
};
T0& GetReference() {
return t0;
};
};
class C
{
ThrowingT0 t0;
T1 t1;
public:
explicit C(int n) : t0(n), t1() {
};
void SomeMemberFunctionUsingT0() {
t0.GetReference().SomeMemberFunction();
};
};