views:

327

answers:

5

Hi, I mentioned in one of my earlier questions that I'm reading book "C++ Coding Standards" By Herb Sutter and Andrei Alexandrescu. In one of the chapters they are saying something like this:

Always perform unmanaged resource acquisition, such as a new expression whose result is not immediately passed to a smart pointer constructor, in the constructor body and not in initializer lists.

Does that mean that I should use construction of this form (providing that data_3_ has to be initialized with new):

SomeClass(const T& value, const U& value2, const R& value3)
    : data_(value), data_2_(value2)
{
    data_3_ = new value3;
}

instead of:

SomeClass(const T& value, const U& value2, const R& value3)
    : data_(value), data_2_(value2), data_3_(new value3)
    // here data_3_ is initialized in ctor initialization list
    // as far as I understand that incorrect way according to authors
{
}

Thanks in advance.

P.S. And if that's what they mean why are they using term unmanaged resource acquisition? I always thought that this resources are "manually managed"?

P.S 2. I'm sorry in advance if there are any formatting problems in this post - I have to admit - I absolutely detest the way of formatting on this forum.

+2  A: 

That's because a constructor may throw an exception.

In the situation you describe (ie not using a smart pointer), you have to free the resource in the destructor AND if the constructor throws an exceptions, with a try-catch block:

SomeClass(const T& value, const U& value2, const R& value3):data_(value),data_2_(value2) :
data_3_(NULL)
{
    try 
    {
        data_3_ = new value3;
    }
    catch(...)
    {
        delete data_3_;
        throw;
    }
}

.. Which you can't do if an exception is thrown in the initialisation list.

See this for further explanations.

Samuel_xL
yes, my bad. The point is the explanation though -.-
Samuel_xL
Despite the fact that statement: if (data_3_ != NULL) delete data_3_; is useless, his answer is correct. Thanks for your answer. Most appreciated.
There is nothing we can do
Sorry, this isn't true. If the constructor of value3 throws, then the memory allocated by new will be deallocated - see 5.3.4/17 in the C++ DStandard. And even if that was the case, new would not return and data_3_ will not be assigned to, meaning that it cannot be deleted.
anon
@Neil you are wrong. The memory allocated automatically is freed, but the destructor is not called. But hey, you can still say Alexandrescu, Sutter, and the other guys at gotw.ca are wrong; your problem
Samuel_xL
who said it's about constructor of value3 throwing? Neil you're blaming people too fast imho
Gregory Pakosz
@Gregory The only other thing that could throw is new, in which case you still can't delete anything.
anon
The delete is surely wrong -- data_3_ might be uninitialized if `new` throws.
Amnon
@Sam I'm not disagreeeing with Sutter and Alexandrescu, I'm disagreeing with YOU. And unlike you I'm quoting the relevant parts of the C++ Standard to back up my position.
anon
Brock Woolf
It is probably a bad example. The problem only may show if there is an unmanaged resource that is not constructed last.
Alex B
@Charles The destructor cannot be invoked, as the object has not been constructed. But that is not the issue here.
anon
@Neil Well the answer is correct - it describes techique how you should initialize with new in ctor and where you should catch exc. What was incorrect in this example I've already said. I also wouldn't list data_3_ in initializer list but nonetheless the answer is correct.
There is nothing we can do
@Neil correct me if I'm wrong and where I'm wrong but are they not saying the same thing? This same technique is described - ok in answer I accepted there were few mistakes but there were irrelevant - I asked in my original question how one should initialize with new in ctor and answer I accepted answered to my quesition.
There is nothing we can do
It's your right to accept whatever you want, so ignore me.
anon
@atch: This answer describes deleting an object if its own allocation fails, which is wrong. My answer describes deleting an already allocated object if something fails later on in the constructor.
Mike Seymour
@Mike yes I see that but I also got that, from the answer I accepted. Yes of course, your answer is the most proper one and I agree with you but technique you describing is exactly this same.
There is nothing we can do
@Neil I'm not ignoring anyone, I just got what I've asked for, and I accepted that without going into details and pointing out irrelevant mistakes.
There is nothing we can do
@atch: I think the point is that they aren't exactly the same. This answer does not mention that you have to free any other objects which were allocated earlier on in the constructor, which is the whole point of doing the new in the constructor body.
Wesley Petrowski
@Wesley, just like in my answer to Neil and Mike: I asked if I correctly understood what guys in "C++ coding..." mean and I got this answer so I've accepted that.
There is nothing we can do
@Wesley and by the way techniques describes by both Mike and the other fella are in fact the same (of course in first answer there were few mistakes but as I said earlier on to me those mistakes were unrelevant because I've asked where to use new in ctor body or in init. list)
There is nothing we can do
If all you were looking for is a clarification of syntax, then that's ok. I think what everyone else is trying to point out is _why_ you'd want to put it in the ctor body.
Wesley Petrowski
I agree with Neil. The point is that in the catch you do not need to delete the one object that failed construction. Take a look at the answer by Mike Seymour: the first resource does not require a try/catch, as failure to construct that element will, well not have any created element to delete. It is when you have more than one raw pointer that you need to delete the N-1 first elements when the Nth fails. The first element need not be inside the try, and the last element need not be deleted in the catch.
David Rodríguez - dribeas
I agree with Neil. If the construction of data_3_ fails, then if the object was created through a new (as it is, in the example), the allocation is deallocated. Look at: http://www.gotw.ca/gotw/056.htm from the same Herb Sutter you misunderstood, from which I quote: "If step 3 [constructor] fails because of an exception, then the memory allocated for T1 [new] is automatically deallocated (step 1 is undone)".
paercebal
+1  A: 

It's a matter of exception safety.

If for some reason, the constructor fails and throws an exception, you can't clean up behind you.

Let's consider:

SomeClass() : data1(new T1()), data2(new T2()), data3(new T3()) {}

If T2's or T3s constructor throws, you definitely leak the memory corresponding to the initialization of data1. Also, you won't know which allocation raised the exception: was it new T2() or new T3()? and it's such a case you don't know whether it's safe to delete data2; as part of a constructor exception handler.

To write exception safe code, use smart pointers or use try/catch blocks in the body of the constructor.

SomeClass() : data1(new T1()), data2(new T2()), data3(new T3())
{
  data1 = new T1();
  try
  {
    data2 = new T2();
    try
    {
      data3 = new T3();
    }
    catch (std::exception&)
    {
      delete data2;
      throw;
    }
  }
  catch (std::exception&)
  {
    delete data1;
    throw;
  }
}

As you can see, using try/catch blocks is not that readable and likely error prone, compared to using member smart pointers.

Note: "C++ Coding Standards" Chapter 48 refers to "More Exceptional C++" Item 18 which itself refers to Stroustrup's "The Design And Evolution of C++3" Section 16.5 and Stroustrup's "The C++ Programming Language" Section 14.4.

EDIT: "More Exceptional C++ Item 18 has the same content as GotW #66: Constructor Failures. Refer to the web page in case you don't have the book.

Gregory Pakosz
See my comment to Sam's answer.
anon
well the discussion would be easier with a better sample code. See the "Aside: Why Does C++ Do It That Way?" section in http://www.gotw.ca/gotw/066.htm
Gregory Pakosz
that's the link i've pointed to in my answer. Neil needs to understand what happens if a constructor fails in a try-catch block. That's explained in a few FAQs I guess, see Marshall Cline's FAQ, it should be there somewhere. @Neil you misunderstand the standard.
Samuel_xL
+8  A: 

Initialization of manually managed resources may lead to resource leaks if the constructor throws an exception at any stage.

First, consider this code with automatically managed resources:

class Breakfast {
public:
    Breakfast()
        : spam(new Spam)
        , sausage(new Sausage)
        , eggs(new Eggs)
    {}

    ~Breakfast() {}
private:
    // Automatically managed resources.
    boost::shared_ptr<Spam> spam;
    boost::shared_ptr<Sausage> sausage;
    boost::shared_ptr<Eggs> eggs;
};

If "new Eggs" throws, ~Breakfast is not called, but all constructed members' destructors are called in reverse order, that is destructors of sausage and spam.

All resources are properly released, no problem here.

If you use raw pointers (manually managed):

class Breakfast {
public:
    Breakfast()
        : spam(new Spam)
        , sausage(new Sausage)
        , eggs(new Eggs)
    {}

    ~Breakfast() {
        delete eggs;
        delete sausage;
        delete spam;
    }
private:
    // Manually managed resources.
    Spam *spam;
    Sausage *sausage;
    Eggs *eggs;
};

If "new Eggs" throws, remember, ~Breakfast is not called, but rather the destructors of spam and sausage (which are nothing in this cause, because we have raw pointers as actual objects).

Therefore you have a leak.

The proper way of rewriting the code above is this:

class Breakfast {
public:
    Breakfast()
        : spam(NULL)
        , sausage(NULL)
        , eggs(NULL)
    {
        try {
            spam = new Spam;
            sausage = new Sausage;
            eggs = new Eggs;
        } catch (...) {
            Cleanup();
            throw;
        }
    }

    ~Breakfast() {
        Cleanup();
    }
private:
    void Cleanup() {
        // OK to delete NULL pointers.
        delete eggs;
        delete sausage;
        delete spam;
    }

    // Manually managed resources.
    Spam *spam;
    Sausage *sausage;
    Eggs *eggs;
};

Of course, you should instead prefer to wrap every unmanaged resource in a separate RAII class, so you can manage them automatically and group them together into other classes.

Alex B
+11  A: 

The advice is necessary if the class contains two or more unmanaged resources. If allocation of one fails, then you will need to free all the previous allocated resources to avoid a leak. (EDIT: more generally, any exception thrown after allocating a resource has to be handled by deleting that resource). This can't be done if they are allocated in the initialiser list. For example:

SomeClass() : data1(new value1), data2(new value2) {}

will leak the value1 if new value2 throws. You will need to handle this, like so:

SomeClass() : data1(0), data2(0)
{
    data1 = new value1; // could be in the initialiser list if you want
    try
    {
        data2 = new value2;
    }
    catch (...)
    {
        delete data1;
        throw;
    }
}

Of course, all these shenanigans can be avoided by sensible use of smart pointers.

Mike Seymour
I suspect this is the correct answer +1
anon
+1. This IS the correct answer. It has an hidden trap, though: The data in the initializer list is constructed according to the order of declaration of the members in the class declaration, not the order of the initializer list. Whereas, when the allocation is done inside the constructor, the user have control on the order of initialization. Still, the right solution for the main problem is using smart pointers, but then, I guess everyone knew that...
paercebal
+1 As pointed out already, this REALLY IS the correct answer. :-D It has bit me a couple of times before with out of memory exceptions leading to resource leaks. All the more reason to use smart pointers.
+3  A: 

I'm posting this as an answer because it is too long to fit into a comment.

Consider:

A * a;
...
a = new A;

What happens if A's constructor throws?

  • firstly, the A instance being constructed is never fully created
  • that being the case, A's destructor will not be called - there is in fact no A object
  • the memory allocated by new to build the A in is deallocated
  • stack unwinding takes place, this means that the assignment to the pointer 'a' never takes place, it retains its original indeterminate value.

From this it should be obvious that there is nothing to call delete on, no allocated memory, no object of type A. The same logic holds if new throws, except the A constructor will never have been used in the first place.

anon