Бросить исключение из инициализатора конструктора - PullRequest
4 голосов
/ 20 апреля 2010

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

Например:

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 ]

8 голосов
/ 20 апреля 2010

Вы можете 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 не мешал выводу его типа.

2 голосов
/ 20 апреля 2010

Это способ выкинуть из списка инициализатора

C(int n)
    : t0(n > 0 ? n : throw std::runtime_error("barf")),
      t1() {}

Вы говорите "выбросить исключение, если t0 (n) недопустимо". Почему бы вам не выбросить из конструктора T0?

Объект должен быть действительным после строительства.

2 голосов
/ 20 апреля 2010

Думаю, есть несколько способов сделать это. Из того, что я понимаю, 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;
}

Если бы я выбрал, я бы выбрал методы времени выполнения, даже если диапазон был во время компиляции. Даже при низкой оптимизации компилятор будет генерировать один и тот же код, и он будет гораздо менее неуклюжим и, возможно, чище читать, чем версия класса.

1 голос
/ 20 апреля 2010

Просто поместите класс 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();
    };
};
...