Бросить исключение из инициализатора конструктора

Каков наилучший способ вызвать исключение из инициализатора конструктора?

Например:

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();
    };
};
Другие вопросы по тегам