C ++忽略构造函数异常的分支

时间:2010-02-04 00:10:19

标签: c++ exception-handling constructor exception

我搜索过SO以获得答案,但没有找到答案。

当一个对象在构造函数的末尾抛出一个异常时,该对象是有效的还是这个'取决于构造技术'?

示例:

    struct Fraction
    {
      int m_numerator;
      int m_denominator;
      Fraction (double value,
                int denominator);
    };
    Fraction::Fraction(double value, int denominator)
    :  m_numerator(0), m_denominator(denominator)
    {
      if (denominator == 0)
      {
/* E1 */        throw std::logic_error("Denominator is zero.");
      }
      m_numerator = static_cast<int>(value * static_cast<double>(denominator));
      double actual_value = 0.0;
      actual_value = static_cast<double>(m_numerator) / static_cast<double>(m_denominator);
      double error = fabs(actual_value - value);
      if (error > 5.0E-5)
      {
/* E2 */  throw std::logic_error("Can't represent value in exact fraction with given denominator");
      }
    }

该计划:

int main(void)
{
    try
    {
        Fraction f1(3.14159264, 4); // Throws exception, E2 above.
    }
    catch (...)
    {
        cerr << "Fraction f1 not exactly representable as fraction with denom. of 4.\n";
    }

    // At this point, can I still use f1, knowing that it is an approximate fraction?

    return EXIT_SUCCESS;
}

在这个例子中,可以在捕获到异常后使用f1,知道它是一个近似值吗?

已构建并初始化数据成员。

我没有看到任何违反上述规则的C ++语言规则。

修改:将错误增量值从5.0E05更改为5.0E-5。

9 个答案:

答案 0 :(得分:5)

捕捉到异常后,

f1超出范围。它不再存在,所以你不能使用它。

答案 1 :(得分:4)

Jonathan的回答是正确的。此外,虽然分数可能处于有效状态,但我不建议使用异常进行流控制,而尤其用于关于对象状态的通信。相反,请考虑在您的Fraction对象API中添加某种is_exactly_representable,以返回bool

答案 2 :(得分:2)

不,一旦在退出中定义f1的范围,您就不能再使用该对象了。所以,在你的代码中:

try
{
    Fraction f1(3.14159264, 4); // Throws exception, E2 above.

    // f1 can be used until here
}
catch (...)
{
}

// The scope that f1 was defined in is over, so the compiler will not let
// you reference f1

那就是说,也许你应该重新思考当你无法代表时抛出异常 实际价值。因为这可能仅适用于某些用途,您可以要求呼叫者请求它:

enum FractionOption { disallowInexact, allowInexact };

Fraction::Fraction(double value, int denominator,
                   FractionOption option = disallowInexact)
{
    ...
    if ((option == disallowInexact) && (error > 5.0E-5))
    {
        throw std::logic_error("Can't represent value ...");
    }
}

Fraction f1(3.14159264, 4, allowInexact);

答案 3 :(得分:2)

  

throw in constructor = construction failed - &gt;对象无法使用

如前所述,如果抛出异常,则该对象超出范围。但是,您可能对分配对象时的情况感兴趣:

f = new Fraction(3.14159264, 4);

在这种情况下,f也是不可用的,因为构造函数没有完成工作,并且没有指定指针。析构函数不会被调用,内存被解除分配,因此无法使用该对象。

因此,正常构造您的对象,如果您打算使用该类,请不要使用异常。使用is_exact()成员函数来确定构造后它是否完全正确。

答案 4 :(得分:1)

我同意fbrereto。

如果你在构造函数中抛出一个错误,相当于说“构造这个对象不起作用”或“无法创建对象”,那么你需要处理这个事实 - 我只做这对于致命错误,对象无法使用,否则无法打开我们希望能够在MySettingsReader类中打开的文件。

答案 5 :(得分:1)

关注JMD

这是catch子句中的f1。答案也是否定的。所以你看到范围规则阻止你甚至在代码中提出这个问题。

唯一能让对象存在的东西就是如果它的析构函数运行了 - 但是如果构造函数没有完成它就不会运行

答案 6 :(得分:1)

  

当一个对象在构造函数的末尾抛出异常时,是   对象有效还是这个'取决于构造技术'?

是的,确实取决于。我的意思是,这取决于你的意思对象是有效的。有效可能有多种含义。

已知的是,已被中断的构造的对象是部分构造的对象。现在,如果你认为部分构造是无效状态,那么是的,这样的对象将是无效的。

但是根据C ++ / 15.2中指定的方案保证销毁:

  

部分对象   建造或部分毁坏   将为所有人执行析构函数   其完全构建的子对象,   也就是说,对于其中的子对象   构造函数已完成执行   析构函数还没有开始   执行。

这意味着,只有部分构造的对象的子对象才能被正确地破坏,但部分构造的对象本身的析构函数将被调用。

#include <iostream>
using namespace std;
struct A
{
    ~A() { cout<<"~A()\n"; }
};
struct B
{
    A a;
    B() { throw 1; }
    ~B() { cout<<"~B()\n"; } // never called
};
int main()
{
    try
    {
        B a;
    }
    catch (...)
    {
        cout << "caught\n";
    }
}

答案 7 :(得分:0)

如果对象在构造期间抛出异常,则在技术上不会使对象无效。在您的示例中,f1超出范围,因此在抛出异常时将被释放。

如果f1是在try块内部分配和构造的指针,并且构造函数(不是分配器)抛出异常,则指针将是有效的分配内存。该内存中的对象是否包含有效数据将取决于您的构造函数;基本上如果数据在投掷之前有效,它将在投掷之后有效。

另外,听起来你正在尝试做的事情并不适合用于异常,我会在这里质疑你的设计。在构造函数调用中抛出异常通常表示该对象未正确构造且不应使用。

答案 8 :(得分:0)

如果在构造函数期间的任何阶段抛出异常(并且未在构造函数中捕获),则该对象将不存在。已经成功构造的所有成员变量将按照与构造完全相反的顺序进行解构。如果从成员变量构造函数或初始化列表中抛出异常,则无法构造的成员变量没有调用析构函数,也没有任何后面的函数。

在任何情况下,假设您在任何地方使用RAII,所有资源都被正确释放,并且没有可访问的对象。在ptr = new Foo();的情况下,变量ptr保留其旧值。同样地,smartptr.reset(new Foo());根本不会调用重置功能。<​​/ p>

注意在构造其他对象的表达式中使用operator new的谬误:somefunc(Foo(), new Bar());。如果Foo构造函数失败,则可能存在内存泄漏(取决于编译器处理参数的顺序)。