从构造函数初始值设定项中抛出异常的最佳方法是什么?
例如:
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))
。
处理此类案件的做法是什么?
答案 0 :(得分:8)
您可以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
干扰其类型推断。
答案 1 :(得分:2)
我认为有很多方法可以解决这个问题。据我所知,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;
}
如果我选择,即使范围是编译时,我也会选择运行时方法。即使优化程度很低,编译器也会生成相同的代码,并且与类版本相比,它的笨拙和阅读清晰度要低得多。
答案 2 :(得分:2)
这是一种从初始化列表中抛出的方法
C(int n)
: t0(n > 0 ? n : throw std::runtime_error("barf")),
t1() {}
你说“如果t0(n)无效则抛出异常”。 你为什么不抛弃T0的构造函数?
一个物体在施工后应该是有效的。
答案 3 :(得分:1)
将类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();
};
};