简体   繁体   中英

Do smart pointers preclude the need for two-phase construction?

Two-phase construction takes the following shape:

struct something {
    something ()
      : p1(NULL)
      , p2(NULL)
    {   }

    ~something () {
        if (p1) delete p1;
        if (p2) delete p2;
    }

    void initialize () {
        p1 = new int(2);
        p2 = new int(5);    // May throw if allocation fails!
    }

    int* p1;
    int* p2;
};

The point of which being that a naive constructor (that doesn't watch for allocation failures) will leak memory: a partially-constructed object's destructor is never called.

My question: is the following code safe, and by corrolary, do smart pointers obviate two-phase construction?

struct something {
    something ()
      : p1(new int(2))
      , p2(new int(5))
    {   }

    std::unique_ptr<int> p1;
    std::unique_ptr<int> p2;
};

My question: is the following code safe,

Yes, that would be OK.

struct something {
    something ()
      : p(new int(5))
    {   }

    std::unique_ptr<int> p;
};

Note that the naive code

struct something {
    something ()
      : p(new int(5))
    {   }

    int* p;
};

would be exception safe, too, because there's only one allocation which can fail. I think you're talking rather about

struct something {
    something ()
      : p(new int(5)), q(new int)
    {   }

    int *p, *q;
};

which wouldn't. Smart pointers would work in that case, too.

Yes, your new code is fine. Note however that there is a possible subtly in more complex cases:

#include <memory>

struct foo {
  foo(std::shared_ptr<int> a, std::shared_ptr<int> b) { }
};

struct bar {
  foo f;
  bar() : f(std::shared_ptr<int>(new int), std::shared_ptr<int>(new int)) { }
};

int main() {
  bar b;
}

would not be safe however since the order of evaluation of the arguments of the constructor of foo in the initializer list of bar is unspecified. A conforming compiler might choose to do a depth or breadth first order of evaluation (or anything else so long as they all got evaluated correctly in the end). This means that if the first new int succeeded, but the second one throws before the shared_ptr objects got constructed the first allocation to be performed could still leak.

If you find yourself wanting to do this there are two possible solutions, besides just falling back to two-phase construction: the first might be a refactor, the second would be to construct the shared_ptr s individually first as members of bar, before f . Which of those is most appropriate is a judgment call I think needs to be made on a case by case basis.

You don't need two phase construction at all if you simply handle the exception. This is the RIAA way.

struct something {
    something ()
      : p1(NULL)
      , p2(NULL)
    {   
        p1 = new int(2);
        try {
            p2 = new int(5);    // May throw if allocation fails!
        } catch (std::bad_alloc&) {
            delete p1;  //cleanup 
            throw;  //rethrow
        }
    }

    ~something () {
        delete p1;
        delete p2;
    }

    int* p1;
    int* p2;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM