views:

206

answers:

9

I've searched SO for an answer to this, but haven't found one.

When an object throws an exception at the end of the constructor, is the object valid or is this one of those 'depends on the construction technique'?

Example:

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

The program:

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

In this example, can f1 be used after the exception is caught, knowing that it is an approximate value?

The data members have been constructed and initialized.

I don't see any C++ language rule that is violated by the above.

Edit: Changed error delta value from 5.0E05 to 5.0E-5.

+5  A: 

f1 is out of scope after the exception is caught. It no longer exists, so you can't use it.

Jonathan M Davis
Am I correct that the language grammar prevents an object from usage after the constructor fails due to the scope issue? I don't see how to catch the exception without a `try` / `catch` block and the construction in the `try` block.
Thomas Matthews
@Thomas: Correct, you can't catch an exception without a catch block. Had you not had one, it would have left `main` and the operating system would have caught it instead. (Thus giving you those nice crash dialog boxes.)
GMan
@Thomas Matthews The language does prevent you from using any variables which were declared within the scope of the try outside of the try - since then they'd be out of scope. So, you can't even attempt to use such a variable outside of the try block. The only way to get around it would be to use pointers, and then since the object is an invalid state, it should be deleted, not used.
Jonathan M Davis
Actually, on reflection, I'm not sure that you could even get around this with a pointer. Since the constructor failed, the memory would likely never be assigned to the pointer, but would likely be cleaned up instead. So, really, I don't think that there's any way to use an invalidly constructed object. However, you do need to make sure that things are cleaned up in constructor when it fails if you dynamically allocated memory or other resources in it. EDIT: Ah, Kornel Kisielewicz talks about it below.
Jonathan M Davis
@Johnatan - it is automatically deallocated, but the destructor is not called, see my answer.
Kornel Kisielewicz
@Kornel I just noticed your answer. Thanks for the good info.
Jonathan M Davis
The object is technically destructed even before the exception is caught - it is never fully constructed and so the standard guarantees that any members that have been fully constructed are destructed, but note that the destructor for the full object is never called.
villintehaspam
+3  A: 

Jonathan's answer is correct. In addition, while the fraction may be in a valid state, I would not recommend using exceptions for flow control, and especially for communication about the state of an object. Instead, consider adding some kind of is_exactly_representable to your Fraction object API that returns a bool.

fbrereto
Good suggestion about the `is_exactly_representable`. The object is valid, but not completely accurate. Similar to how 1/3 can't be exactly represented in floating point.
Thomas Matthews
+1  A: 

I agree with fbrereto.

If you throw an error in a constructor that is equivalent to saying "constructing this object didn't work" or "object couldn't be created" and as such you then need to handle that fact - I'd only do this for fatal errors for which the object can't be used otherwise, such as couldn't open a file we expected to be able to open in MySettingsReader class.

Ninefingers
+1 Although this isn't the solution I choose, I give it +1 for clarification when to use exceptions.
Thomas Matthews
+2  A: 

No, once the scope that f1 is defined in exits, you can no longer use the object. So, in your code:

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

That said, perhaps you should rethink throwing an exception when you can't represent the actual value. Because that may only be applicable for certain uses, you could require the caller to request it:

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);
R Samuel Klatchko
+1, I like the added policy to the constructor. That didn't cross my mind. {I though policies were only used with templates, such as in *Modern C++ Design* by Andrei Alexandrescu.}
Thomas Matthews
A: 

If an object throws an exception during construction it does not technically invalidate the object. In your example f1 goes out of scope and is therefore deallocated when the exception is thrown.

If f1 were a pointer that were allocated and constructed inside the try block, and the constructor (not the allocator) threw an exception your pointer would be to valid, allocated memory. Whether or not the object in that memory contained valid data would be dependent on your constructor; basically if the data was valid before the throw it would be valid after the throw.

Also it really sounds like what you're trying to do is not an appropriate use for exceptions, and I'd question your design here. Throwing an exception in a constructor call generally signifies that the object wasn't properly constructed and shouldn't be used.

Chris
"your pointer would be to valid, allocated memory" -- This is incorrect.
Kornel Kisielewicz
Allocations roughly translate from: `foo* f = 0; f = new foo()` to: `foo * f = 0; void* __memory = 0; foo* __dummy = 0; try{ __memory = ::operator new(sizeof(f)); __dummy = new (__memory) foo(); }catch(...){::operator delete(__memory); throw;} f = __dummy;` And yes, I'm well-aware that looks gross. :P But a pointer remains unchanged if `new` throws; and memory is free'd.
GMan
+1  A: 

following JMD

THen is f1 available in the catch clause. THe answer is also no. SO you see that the scoping rules prevent you from even asking this question in code.

The only thing that would give away that the object existed would be if its destructor ran - but it does not run if the contructor did not complete

pm100
+2  A: 

throw in constructor = construction failed --> object unusable

As already noted, if the exception is thrown, then the object goes out of scope. However, you might be interested in the case when you allocate an object:

f = new Fraction(3.14159264, 4);

In this case, f is also unusable, because the constructor didn't finish work, and the pointer didn't get assigned. The destructor doesn't get called, and memory is deallocated, hence there's no way to use the object.

Hence, construct your object normally, don't use exceptions if you intend to use the class. Use a is_exact() member function to decide if it's exact after construction.

Kornel Kisielewicz
The destructor isn't called if the object is created on the stack either.
villintehaspam
@villintehaspam, true, I was focusing on the uncovered variant.
Kornel Kisielewicz
The important fact is that if constructor of Fraction throws, then the assignment to `f` pointer is never accomplished, so memory address returned by `new` is in fact not accessible, regardless of what happens with allocated memory.
mloskot
+1  A: 

When an object throws an exception at the end of the constructor, is the object valid or is this one of those 'depends on the construction technique'?

Yes, it depends indeed. I mean, it depends what you mean object is valid. Valid may have number of meanings.

What is known is that an object which construction has been interrupted is a partially constructed object. Now, if you consider partial construction as invalid state, then yes, such object will be invalid.

The destruction is guaranteed however according to this scheme specified in C++/15.2:

An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the constructor has completed execution and the destructor has not yet begun execution.

It means, that only subobjects of partially constructed object will be properly destructed but destructor of partially constructed object itself will not be called.

#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";
    }
}
mloskot
A: 

If at any phase during constructor an exception is thrown (and not caught within the constructor), the object will not exist. All member variables that have already been constructed successfully will be deconstructed in the exact reverse order of construction. If the exception was thrown from a member variable constructor or otherwise within initialization list, the member variable that failed to construct does not have its destructor called, and neither have any that come after it.

In any case, assuming that you use RAII everywhere, all resources are correctly freed and there will be no object to access. In the case of ptr = new Foo(); the variable ptr retains its old value. Similarly smartptr.reset(new Foo()); will not call the reset function at all.

Notice the fallacy of using operator new in expressions that construct other objects: somefunc(Foo(), new Bar());. If the Foo constructor fails, there may be a memory leak (depending on the order in which your compiler processed the arguments).

Tronic