从构造函数初始化程序抛出异常

4

如何最好地从构造函数初始化器中抛出异常?

例如:

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))
处理这种情况的实践方法是什么?

T0 如何通知失败?例如,是否有像 bool bad() 这样的函数? - GManNickG
@GMan T0不会通知失败。它基本上是一个int结构体,其值可能过大,超出硬件处理能力(CUDA中的块尺寸乘以寄存器使用量)。 - Anycorn
嗯,那么动态类型的有效范围是什么? - GManNickG
@GMan 是的,但我可以使用你或Billy的解决方案。 - Anycorn
无关紧要,但您可能希望将 C 的构造函数声明为“显式”。 - Billy ONeal
4个回答

8

您可以从初始化t0t1的表达式中进行throw,或者任何至少需要一个参数的构造函数。

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干扰其类型推断。

只是提醒一下,即使class empty没有数据成员,它仍会产生指针大小的开销。 - Billy ONeal
不错啊... 如果可以的话,我会给这个2分! - ϹοδεMεδιϲ

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

如果我要选择,即使范围是编译时,我也会选择运行时方法。即使没有进行高度优化,编译器将生成相同的代码,而且与类版本相比,它更加简洁易读。请保留HTML标签。

好的,这比函数包装器更简洁。快速思考我的问题告诉我这是一个好的解决方案。 - Anycorn
顺便说一句,这个聪明的东西绝对会被我加入到我的解决方案工具包中。 - Anycorn
你的解决方案给了我一些关于如何优化某些事情的不相关想法。谢谢。 - Anycorn
GManNickG,我不熟悉你在这个模板类中使用的“函数对象”的语法。请解释一下这个语法。我知道它在做什么,但我不确定机制是如何工作的。让我困惑的是:operator const value_type&(void) const。你是在重载“const”吗?为什么这个函数对象没有两组括号? - Andrew Falanga
@AndrewFalanga:这是一个转换运算符,就像这样:struct foo { operator float() { return 5; } }; foo x; float y = x; - GManNickG

2
这是一种从初始化列表中抛出异常的方法。
C(int n)
    : t0(n > 0 ? n : throw std::runtime_error("barf")),
      t1() {}

你的意思是“如果t0(n)无效,则抛出异常”。 为什么不在T0的构造函数中抛出呢?
一个对象在构造后应该是有效的。

在编程中,需要在 throw 之后加逗号以避免传递 void - Potatoswatter
@Potato:我不理解你的意思。 - GManNickG
throw没有返回类型,这并不重要,因为我们正在离开大楼。另一方面,有编译器。 - Eddy Pronk
1
哎呀,5.16/3是条件运算符内部抛出表达式的一个特殊情况。不要紧。哈哈,我正在自我纠正的同时,也被其他人在这个问题上纠正着。 - Potatoswatter
@eddy,该对象无效是因为硬件可能不喜欢其中的值。它是一个与实际硬件分离的配置参数。 - Anycorn
显示剩余2条评论

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接