代码之家  ›  专栏  ›  技术社区  ›  Thomas Matthews

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

  •  5
  • Thomas Matthews  · 技术社区  · 15 年前

    我找过这个问题的答案,但没有找到。

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

    例子:

        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是一个近似值,那么可以在捕获异常后使用f1吗?

    数据成员已被构造和初始化。

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

    编辑: 将错误增量值从5.0e05更改为5.0e-5。

    9 回复  |  直到 15 年前
        1
  •  4
  •   fbrereto    15 年前

    乔纳森的回答是正确的。此外,虽然分数可能处于有效状态,但我不建议对流控制使用异常,以及 尤其地 用于对象状态的通信。相反,考虑添加一些 is_exactly_representable 返回一个 bool .

        2
  •  5
  •   Jonathan M Davis    15 年前

    捕获异常后,F1超出范围。它已经不存在了,所以你不能使用它。

        3
  •  2
  •   R Samuel Klatchko    15 年前

    不,一旦在出口中定义了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);
    
        4
  •  2
  •   Kornel Kisielewicz    15 年前

    throw-in constructor=构造失败-->对象不可用

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

    f = new Fraction(3.14159264, 4);
    

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

    因此,通常构造对象,如果您打算使用类,则不要使用异常。使用is-exact()成员函数来决定构造后是否精确。

        5
  •  1
  •   user257111    15 年前

    我同意费布雷托的观点。

    如果您在构造函数中抛出一个错误,相当于说“构造此对象不起作用”或“无法创建对象”,因此您需要处理这个事实-我只会针对无法使用该对象的致命错误执行此操作,否则,例如无法在mysettingsreader cl中打开我们希望能够打开的文件。屁股。

        6
  •  1
  •   pm100    15 年前

    遵循JMD

    那么在catch子句中f1是可用的。答案也是否定的,所以您会看到范围规则甚至阻止您在代码中问这个问题。

    唯一会泄露这个物体存在的东西是如果它的析构函数运行的话——但是如果这个构造函数没有完成,它就不会运行。

        7
  •  1
  •   mloskot    15 年前

    当对象在构造函数末尾引发异常时,是 对象是有效的还是“取决于构造技术”?

    是的,它 取决于 的确。我是说,这取决于你的意思 对象有效 . 有效可以有多个含义。

    众所周知,被构造中断的对象是部分构造的对象。现在,如果您将部分构造视为无效状态,那么是的,这样的对象将是无效的。

    然而,根据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";
        }
    }
    
        8
  •  0
  •   Beanz    15 年前

    如果一个对象在构造过程中抛出一个异常,它不会在技术上使该对象失效。在您的示例中,f1超出了作用域,因此在引发异常时被释放。

    如果f1是在try块内分配和构造的指针,并且构造函数(而不是分配器)引发异常,则指针将指向有效的已分配内存。内存中的对象是否包含有效数据将取决于您的构造函数;基本上,如果数据在引发之前是有效的,那么在引发之后是有效的。

    而且,听起来您试图做的事情不适合用于例外情况,我会在这里质疑您的设计。在构造函数调用中引发异常通常表示对象没有正确构造,不应使用。

        9
  •  0
  •   Tronic    15 年前

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

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

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