问题
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?
回答1:
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.
回答2:
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.
回答3:
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.
回答4:
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();
};
};
来源:https://stackoverflow.com/questions/2672398/throw-exception-from-constructor-initializer