20

I used to write code like this:

class P {};

class Q: public P {};

class A {
    // takes ownership
    A(P* p): p_(p) {}

    scoped_ptr<P> p_;
};

A a(new Q);

With C++0x, should I rewrite class A as:

class A {
    // takes ownership
    A(unique_ptr<P>&& p): p_(p) {}

    unique_ptr<P> p_;
};
Neil G
  • 32,138
  • 39
  • 156
  • 257
  • 1
    Similarly, is there a C++0x replacement for `boost::scoped_array`? – rafak Jun 11 '10 at 14:46
  • 2
    @rafak `std::unique_ptr` works with arrays as well (it will call delete[]) – Cubbi Jun 11 '10 at 15:56
  • 16
    @rafak: use unique_ptr like this `std::unique_ptr

    `. Not only will unique_ptr call delete[] when it's deallocated, but it disables the * and -> operators instead it provides a [] operator.

    – deft_code Jun 11 '10 at 18:02

5 Answers5

44

I've upvoted comonad's answer, but with a caveat:

Whenever you want to explicitely disallow move semantics, use a scoped_ptr const unique_ptr.

I have not come across any use cases where a const std::unique_ptr is inferior to a boost::scoped_ptr. However I'm open to education on the subject.

Edit:

Here is a use case of boost::scoped_ptr that I think should fail, but does not. It does fail for std::unique_ptr:

#include <iostream>

#ifdef USE_UNIQUEPTR

#include <memory>
typedef std::unique_ptr<int> P;

#else  // USE_UNIQUEPTR

#include <boost/scoped_ptr.hpp>
typedef boost::scoped_ptr<int> P;

#endif  // USE_UNIQUEPTR

int main()
{
    P p1(new int(1));
    {
        // new scope
#ifdef USE_UNIQUEPTR
        const P p2(new int(2));
#else  // USE_UNIQUEPTR
        P p2(new int(2));
#endif  // USE_UNIQUEPTR
        swap(p1, p2);  // should fail!
    }
    std::cout << *p1 << '\n';
}

If the promise of boost::scoped_ptr is that its resource will not escape the current scope, then it is not as good at holding that promise as a const std::unique_ptr. If we want to compare const boost::scoped_ptr to const::std::unique_ptr, I have to ask: for what purpose? They seem the same to me, except that a const std::unique_ptr allows customized construction and destruction.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 1
    Nice trick! I did not think about using any const ptr. Actually, in some cases where I want to forbid copy and move semantics, I still want to use swap semantics. It is just that I do not want any null pointers – not even in race conditions. Without allowing swap semantics, would there be any difference between const scoped_ptr and const unique_ptr? – comonad Apr 04 '11 at 13:59
  • 1
    The only difference I can think of between const scoped_ptr and const unique_ptr is that the latter allows for a custom deallocator, and a custom pointer type. If you want to use swap, but don't want move or copy, then scoped_ptr sounds like the best fit. However scoped_ptr allows nulls as easily as unique_ptr. You may want a smart pointer that hasn't been discussed yet (and I haven't seen): not_null_ptr. – Howard Hinnant Apr 04 '11 at 14:40
  • a `scoped_ptr` can be reset to a different pointer. Does `const unique_ptr` allow that? – balki Sep 02 '12 at 19:02
  • Nope. A `const unique_ptr` says: I'm holding this pointer from construction until destruction. – Howard Hinnant Sep 03 '12 at 16:12
30
  • A auto_ptr is a pointer with copy and with move semantics and ownership (=auto-delete).
  • A unique_ptr is a auto_ptr without copy but with move semantics.
  • A scoped_ptr is a auto_ptr without copy and without move semantics.

    auto_ptr‍s are allways a bad choice – that is obvious.

    Whenever you want to explicitely have move semantics, use a unique_ptr.

    Whenever you want to explicitely disallow move semantics, use a scoped_ptr.

  • All pointers allow swap semantics, like p.swap(q). To disallow those, use any const …_ptr.

There are situations, where you want to use a scoped_ptr pointing to one of several interchangeable objects: Because of the absence of move semantics, it is quite safe (in respect to obvious bugs) that it will not accidentally point to null because of an unintended move. Worth to mention: scoped_ptr‍s can still be swap‍ped efficiently. To make it movable and/or copyable – but still with these swap semantics – you might want to consider using a shared_ptr pointing to a scoped_ptr pointing to an exchangeable (via scoped_ptr::swap) object.

See stackoverflow:smart-pointers-boost-explained for further details.

Community
  • 1
  • 1
comonad
  • 5,134
  • 2
  • 33
  • 31
  • I agree with you, and it looks like you agree with James' answer. Thanks for taking the time to answer... – Neil G Mar 14 '11 at 01:50
2

IMO it is better to use unique_ptr as it provides an additional feature: move semantics. i.e. you can write a move constructor, etc for your class, unlike scoped_ptr. Also, unique_ptr doesn't have an overhead associated with it as it is the case with scoped_ptr, so it is a superior facility. A decision of a rewrite is up to you of course, in case you don't need move semantics then there is no point of the rewrite. Don't forget that unique_ptr is from the standard library, so it must be provided with any compliant implementation of C++0x(when it becomes reality of course :)!

Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234
  • 5
    `scoped_ptr` does not incur overhead. You're probably thinking of `shared_ptr` :) – Billy ONeal Jun 11 '10 at 03:05
  • @Billy ONeal Sometimes I don't know how to express something in English :) Basically, I wanted to say, both don't create overhead. – Khaled Alshaya Jun 11 '10 at 03:20
  • If they both don't create overhead, then why is one "superior" compared to the other? (Referencing -> "doesn't have an overhead associated with it as it is the case with scoped_ptr, so it is a **superior** facility") – Billy ONeal Jun 11 '10 at 03:44
2

I have to disagree with AraK on one being superior. There is no such thing as a superior choice between the two as it often depends on usage. That's like saying a SmartCar is superior to a pick-up truck for all uses because it's lighter and faster. In reality, sometimes you need a truck and sometimes you don't. Your choice of pointer should be based on what you need.

The nice thing about scoped_ptr is it adds a level of safety. By using scoped_ptr you are delcaring that the memory created will exist only for that scope and no more, thus you get compile-time protection against attempting to move it or transfer it.

So, if you want to create somethign but limit it's scope, used scoped_ptr. If you want to create something and have ownership be movable, use unique_ptr. If you want to create something and share that pointer and cleanup when all referencers are gone, use shared_ptr.

  • Sorry, but this is dead wrong. The biggest problem is that `unique_ptr` is in the standard, while `scoped_ptr` is not. Therefore, if a C++0x compiler is suitable for your application, you should always use `unique_ptr`. – Billy ONeal Sep 10 '10 at 15:02
  • 3
    @Billy It might be better for someone with your experience to be extra kind with new members of our community who take the time to answer questions ;) – Neil G Mar 14 '11 at 01:49
1

Edit: my bad, you DO need to write move(p) inside the initialiser. std::move treats whatever it's given as an rvalue reference, and in your case, even though your argument is an rvalue reference to something, passing it to something else (like p_'s constructor) will pass an lvalue reference, never an rvalue reference by default.

Per Karu's comment, also added necessary includes to made my code compilable.

For example:

#include <memory>
#include <cassert>
#include <vector>
using namespace std;

class A {};

class B {
public:
  void takeOwnershipOf(unique_ptr<A>&& rhs) {
    // We need to explicitly cast rhs to an rvalue when passing it to push_back
    // (otherwise it would be passed as an lvalue by default, no matter what
    // qualifier it has in the argument list).  When we do that, the move
    // constructor of unique_ptr will take ownership of the pointed-to value
    // inside rhs, thus making rhs point to nothing.
    owned_objects.push_back(std::move(rhs));
  }

private:
  vector<unique_ptr<A>> owned_objects;
};

int main() {
  unique_ptr<B> b(new B());
  // we don't need to use std::move here, because the argument is an rvalue,
  // so it will automatically be transformed into an rvalue reference.
  b->takeOwnershipOf( unique_ptr<A>(new A()) );

  unique_ptr<A> a (new A());
  // a points to something
  assert(a);
  // however, here a is an lvalue (it can be assigned to). Thus we must use
  // std::move to convert a into an rvalue reference.
  b->takeOwnershipOf( std::move(a) );
  // whatever a pointed to has now been moved; a doesn't own it anymore, so
  // a points to 0.
  assert(!a);
  return 0;
}

Also, in your original example, you should rewrite class A like this:

class A { // takes ownership A(unique_ptr

&& p): p_(std::move(p)) {}

unique_ptr<P> p_;

};

Dan
  • 3,490
  • 2
  • 22
  • 27
  • @Dan: In your example, `owned_objects.push_back(rhs);` will not call the rvalue version of `push_back`. You still need to say `owned_objects.push_back(std::move(rhs));` for that to happen. `rhs` is an rvalue reference (ie. that is the type of the variable `rhs`), but the expression `rhs` yields an lvalue (ie. you could say `rhs = something;` or `&rhs;` etc.). It can be confusing, but when an rvalue reference variable is mentioned as an expression, the result is an lvalue. – Karu Feb 10 '12 at 07:01