Search code examples
c++c++11pointersmemory-managementclass-design

How to safely pass objects created by new into constructor


I have a few classes which look like that:

struct equation {};

struct number: equation {
    number(int n): value(n) {}

  private:
    int value;
};

struct operation: equation {
    operation(const equation* left, const equation* right)
        : left(left), right(right) {}

  private:
    std::unique_ptr<equation> left, right;
};

They are designed in a way that operation takes ownership on the pointers that are passed into constructor.

My question is how can I modify this class to be able to safely use it in a next manner:

operation op(new number(123), new number(456));

It seems to me that if the first object is created and the second one is not (say exception is thrown from number constructor) then it's a memory leak - nobody will ever delete a pointer to the first number.

What can I do with this situation? I do not want to allocate objects sequentially and delete them if something has failed - it's too verbose.


Solution

  • I do not want to allocate objects sequentially and delete them if something has failed - it's too verbose.

    Yes. You just need to apply smart pointer idiom more thoroughly; more precisely, change the parameter type to std::unique_ptr, and use std::make_unique (since C++14) (instead of using new explicitly) to avoid this problem. e.g.

    struct operation: equation {
        operation(std::unique_ptr<equation> left, std::unique_ptr<equation> right)
            : left(std::move(left)), right(std::move(right)) {}
    
      private:
        std::unique_ptr<equation> left, right;
    };
    

    then

    operation op(std::make_unique<number>(123), std::make_unique<number>(456));
    

    Note that the using of std::make_unique is important here, the raw pointer created inside std::make_unique is guaranteed to be managed by the returned std::unique_ptr; even the 2nd std::make_unique fails the std::unique_ptr created by the 1st std::make_unique will see to it that the pointer it owns is destroyed. And this is also true for the case that the 2nd std::make_unique is invoked first.

    Before C++14 you can make your own version of std::make_unique; a basic one is easy to write. Here's a possible implementation.

    // note: this implementation does not disable this overload for array types
    template<typename T, typename... Args>
    std::unique_ptr<T> make_unique(Args&&... args)
    {
        return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
    }