【问题标题】:Throw exception from constructor initializer从构造函数初始化程序抛出异常
【发布时间】:2011-02-09 23:20:38
【问题描述】:

从构造函数初始化器抛出异常的最佳方法是什么?

例如:

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()之类的功能?
  • @GMan T0 不通知失败。它基本上是 int 结构,其值对于硬件来说可能太大而无法处理(CUDA 中的块尺寸乘以寄存器使用量)。
  • 嗯,那么动态类型的有效范围是多少?
  • @GMan 是的,但我可以使用您或 Billy 的解决方案
  • 不相关,但你可能想制作C的构造函数explicit

标签: c++ exception ctor-initializer


【解决方案1】:

您可以从初始化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 干扰其类型推导。

【讨论】:

  • +1。仅供参考,class empty 会产生指针大小的开销,尽管它没有数据成员。
  • 不错的...如果可以的话,我会为此给2分!
【解决方案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;
}

如果我要选择,我会选择运行时方法,即使范围是编译时。即使优化程度较低,编译器也会生成相同的代码,而且它比类版本更不笨拙且可读性更强。

【讨论】:

  • 好的,这比函数包装器更干净。快速想想我的问题告诉我这是一个很好的解决方案
  • 顺便说一句。肯定会进入我的解决方案工具包
  • 你的解决方案给了我一些关于如何优化某些想法的无关想法。谢谢
  • GManNickG,我不熟悉您用于 ?functor? 的语法在这个模板类中。请解释语法。我知道它在做什么,我只是不确定机制是如何工作的。让我绊倒的是:operator const value_type&(void) const。你在重载“const”吗?为什么这个函子没有两组括号?
  • @AndrewFalanga:这是一个转换运算符,如下所示:struct foo { operator float() { return 5; } }; foo x; float y = x;
【解决方案3】:

这是一种从初始化列表中抛出的方法

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

您是在说“如果 t0(n) 无效则抛出异常”。 为什么不从 T0 的构造函数中抛出?

一个对象在构造之后应该是有效的。

【讨论】:

  • throw 后面需要一个逗号以避免传递void
  • @Potato:我没明白你的意思。
  • throw 没有返回类型,也没关系,因为我们要离开大楼。另一方面,还有编译器。
  • @Eddy, @GMan: throw 返回 void?: 返回其替代类型转换为另一个的任何一个。 int 转换为voidvoid 不转换为int 所以整个表达式的结果是void
  • 糟糕,5.16/3 是条件运算符中抛出表达式的特殊情况。没关系。 — 大声笑,我在纠正自己的同时也在被其他人纠正这个问题。
【解决方案4】:

只需将类 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();
    };
};

【讨论】:

    猜你喜欢
    • 2013-07-07
    • 1970-01-01
    • 2011-11-04
    • 2012-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-14
    相关资源
    最近更新 更多