4

What is the best way to throw exception from the constructor initializer?

For example:

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() {}
};

I thought maybe making wrapper, e.g. t0(throw_if_invalid(n)).

What is the practice to handle such cases?

Anycorn
  • 50,217
  • 42
  • 167
  • 261

4 Answers4

8

You can throw from the expression(s) that initialize t0 or t1, or any constructor that takes at least one argument.

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
      {}
};

Note that a throw expression has void type, making throw more like an operator than a statement. The ?: operator has a special case to prevent that void from interfering with its type deduction.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
2

There are multiple ways of going about this, I think. From what I understand, n can only take on a specific range of numbers. For that, you might prevent the constructor from even being run:

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;
};

Could be more fleshed out, but that's the idea. Now you can clamp the range:

struct foo_c
{
    foo_c(ranged_value_c<int, 0, 100> i) :
    x(i)
    {}

    int x;
};

If you pass a value that does not lie from 0-100, the above would throw.


At runtime, I think your original idea was best:

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;
}

And that's it. Same as above, but 0 and 100 can be replaced with a call to some function that returns the valid minimum and maximum.

If you do end up using a function call to get valid ranges (recommended, to keep clutter to a minimum and organization higher), I'd add an overload:

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
}

To allow stuff like this:

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;
}

If I were to choose, I'd pick the runtime methods even if the range was compile-time. Even with low optimization the compiler will generate the same code, and it's much less clumsy and arguably cleaner to read than the class version.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • okay, this is cleaner than function wrapper. quick think about my problem tells me it is a good solution – Anycorn Apr 20 '10 at 04:05
  • clever thing by the way. definitely goes into my solutions Kit – Anycorn Apr 20 '10 at 04:12
  • your solution gave me some unrelated idea of how to optimize certain thinks. Thanks – Anycorn Apr 20 '10 at 04:52
  • GManNickG, I'm unfamiliar with the syntax you've used for the ?functor? in this template class. Please explain the syntax. I know what it's doing, I'm just not sure how the mechanics work. What's tripping me up is: operator const value_type&(void) const. Are you overloading "const"? Why aren't there two sets of parentheses for this functor? – Andrew Falanga May 30 '12 at 14:37
  • @AndrewFalanga: That's a conversion operator, like in this: `struct foo { operator float() { return 5; } }; foo x; float y = x;`. – GManNickG May 30 '12 at 21:21
2

This is a way to throw from the initializer list

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

You're saying "throw exception if t0(n) is not valid". Why don't you throw from the constructor of T0?

An object is supposed to be valid after construction.

Eddy Pronk
  • 6,527
  • 5
  • 33
  • 57
  • You need a comma after the `throw` to avoid passing `void`. – Potatoswatter Apr 20 '10 at 04:20
  • @Potato: I don't see what you mean. – GManNickG Apr 20 '10 at 04:35
  • throw doesn't have a return type and it doesn't matter because we are leaving the building. On the other hand, there are compilers. – Eddy Pronk Apr 20 '10 at 04:42
  • @Eddy, @GMan: throw returns `void` and `?:` returns whichever of its alternative types converts to the other. `int` converts to `void` and `void` does not convert to `int` so the result of the whole expression is `void`. – Potatoswatter Apr 20 '10 at 04:46
  • @Potato: In a conditional operator, a throw-expression will take on the type of the other branch in the ternary. Neil asked this once: http://stackoverflow.com/questions/1212978/in-c-if-throw-is-an-expression-what-is-its-type :) – GManNickG Apr 20 '10 at 04:51
  • 1
    Oops, 5.16/3 is a specific special case for throw-expressions inside the conditional operator. Never mind. — lol, I'm on a roll correcting myself simultaneously with being corrected by others on this question. – Potatoswatter Apr 20 '10 at 04:52
  • @eddy the object is invalid in a sense that hardware may not like values in it. it is a configuration parameter divorced from actual hardware – Anycorn Apr 20 '10 at 04:57
1

Just wrap class T0 inside another class which does throw in cases like this:

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();
    };
};
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552