Home > Software design >  Throwing exception from a function parameter
Throwing exception from a function parameter

Time:09-03

Does the code below produce a memory leak?

#include <utility>
#include <stdexcept>

struct A {};

struct B {
    B() {
        throw std::runtime_error("");
    }
};

template <class T>
class MyPtr {
public:
    MyPtr(T* p) : m_p(p) {}

    MyPtr(MyPtr&& other) : m_p(other.m_p) {
        other.m_p = nullptr;
    }

    ~MyPtr() {
        delete m_p;
    }

private:
    T* m_p;
};

class Foo {
public:
    Foo(MyPtr<A> a, MyPtr<B> b) : m_a(std::move(a)), m_b(std::move(b)) {}

private:
    MyPtr<A> m_a;
    MyPtr<B> m_b;
};

int main() {
    try {
        Foo foo(new A(), new B());
    }
    catch (const std::exception&) {
    }

    return 0;
}

Is there a difference between

Foo foo(new A(), new B());

and

Foo foo(MyPtr(new A()), MyPtr(new B()));

?

CodePudding user response:

The evaluation order is based on sequenced-before and sequenced-after in modern C versions. This means, the following will happen:

  • in any non-overlapping order: new A(), new B() - memory is allocated for A and B and constructors are run (these cannot be interleaved)
  • since B has a ctor, this ctor is run (sequenced-after allocation)
  • B's ctor throws
  • since B's ctor didn't complete, the memory allocated by new for constructing B will be freed, without calling dtor.

Note that the call to Foo's ctor would be sequenced-after the above, so it won't be reached.

Thus, B is not leaked. However, function parameters can be sequenced in any non-overlapping way, so A might or might not be leaked. This might, in theory, change from run to run.

If you do: Foo foo(MyPtr(new A()), MyPtr(new B()));, then you immediately encapsulate the pointers to smart pointers after new, so it won't leak.

Also, it's worth mentioning, while the memory for B is deallocated, any memory that's allocated in B's ctor and not deallocated till throw can still potentially leak. In this example there's no such allocation, but in actual codes it might occur. The only memory deallocated is that of the entire object being constructed.

CodePudding user response:

Since C 17, no leak occurs in either case, but this interpretation hinges on the definition of initialization of a parameter. Does the initialization of a begin by evaluating new A(), or does it start after that expression has already been reduced to a pointer value? CWG2599 answers this question with the broader choice, as makes sense especially for aggregate initialization where the parameter obviously already exists (albeit only partially initialized) before all the initializer-clauses have been evaluated at all.

  • Related