Smart pointers: who owns the object? [closed]
Asked Answered
R

11

122

C++ is all about memory ownership - aka ownership semantics.

It is the responsibility of the owner of a chunk of dynamically allocated memory to release that memory. So the question really becomes who owns the memory.

In C++ ownership is documented by the type a raw pointer is wrapped inside thus in a good (IMO) C++ program it is very rare (rare, not never) to see raw pointers passed around (as raw pointers have no inferred ownership thus we can not tell who owns the memory and thus without careful reading of the documentation you can't tell who is responsible for ownership).

Conversely, it is rare to see raw pointers stored in a class each raw pointer is stored within its own smart pointer wrapper. (N.B.: If you don't own an object you should not be storing it because you can not know when it will go out of scope and be destroyed.)

So the question:

  • What type of ownership semantic have people come across?
  • What standard classes are used to implement those semantics?
  • In what situations do you find them useful?

Lets keep 1 type of semantic ownership per answer so they can be voted up and down individually.

Summary:

Conceptually, smart pointers are simple and a naive implementation is easy. I have seen many attempted implementations, but invariably they are broken in some way that is not obvious to casual use and examples. Thus I recommend always using well tested smart pointers from a library rather than rolling your own. std::auto_ptr or one of the Boost smart pointers seem to cover all my needs.

std::auto_ptr<T>:

Single person owns the object. Transfer of ownership is allowed.

Usage: This allows you to define interfaces that show the explicit transfer of ownership.

boost::scoped_ptr<T>

Single person owns the object. Transfer of ownership is NOT allowed.

Usage: Used to show explicit ownership. Object will be destroyed by destructor or when explicitly reset.

boost::shared_ptr<T> (std::tr1::shared_ptr<T>)

Multiple ownership. This is a simple reference counted pointer. When the reference count reaches zero, the object is destroyed.

Usage: When an object can have multiple owers with a lifetime that can not be determined at compile time.

boost::weak_ptr<T>:

Used with shared_ptr<T> in situations where a cycle of pointers may happen.

Usage: Used to stop cycles from retaining objects when only the cycle is maintaining a shared refcount.

Retouch answered 18/9, 2008 at 16:35 Comment(5)
?? What was the question?Numbat
I just wanted to point out that since this question was posted auto_ptr has been deprecated in favor of (the now standarized) unique_ptrMerits
In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) Can this be rephrased? I don't understand it at all.Marchand
@lololololol You cut the sentence in half. In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around. RAW pointers don't have ownership semantics. If you don't know the owner you don't know who is responsible for deleting the object.There are several standard classes that are used to wrap pointers (std::shared_ptr, std::unique_ptr etc) that define the ownership and thus define who is responsible for deleting the pointer.Retouch
In C++11+ DO NOT USE auto_ptr! Use unique_ptr instead!Magnetism
E
21

For me, these 3 kinds cover most of my needs:

shared_ptr - reference-counted, deallocation when the counter reaches zero

weak_ptr - same as above, but it's a 'slave' for a shared_ptr, can't deallocate

auto_ptr - when the creation and deallocation happen inside the same function, or when the object has to be considered one-owner-only ever. When you assign one pointer to another, the second 'steals' the object from the first.

I have my own implementation for these, but they are also available in Boost.

I still pass objects by reference (const whenever possible), in this case the called method must assume the object is alive only during the time of call.

There's another kind of pointer that I use that I call hub_ptr. It's when you have an object that must be accessible from objects nested in it (usually as a virtual base class). This could be solved by passing a weak_ptr to them, but it doesn't have a shared_ptr to itself. As it knows these objects wouldn't live longer than him, it passes a hub_ptr to them (it's just a template wrapper to a regular pointer).

Ellipsis answered 18/9, 2008 at 17:7 Comment(5)
Instead of creating your own pointer class (hub_ptr), why don't you just pass *this to these objects and let them store it as a reference? Since you even acknowledge that the objects will be destroyed at the same time as the owning class, I don't understand the point of jumping through so many hoops.Jahncke
It's basically a design contract to make the things clear. When the child object receives the hub_ptr, it knows that the pointed object won't be destroyed during the child's lifetime, and has no ownership to it. Both the contained and container objects agree to a clear set of rules. If you use a naked pointer, the rules can be documented, but won't be enforced by the compiler and the code.Ellipsis
Also note that you can have #ifdefs to make hub_ptr be typedef'd to a naked pointer in release builds, so the overhead will exist only in the debug build.Ellipsis
Note that the Boost documentation contradicts your description of scoped_ptr. It states that it is noncopyable and that ownership can not be transferred.Spiceberry
@Alec Thomas, You're right. I was thinking about auto_ptr and wrote scoped_ptr. Corrected.Ellipsis
G
25

Simple C++ Model

In most modules I saw, by default, it was assumed that receiving pointers was not receiving ownership. In fact, functions/methods abandoning ownership of a pointer were both very rare and explicitly expressed that fact in their documentation.

This model assumes that the user is owner only of what he/she explicitly allocates. Everything else is automatically disposed of (at scope exit, or through RAII). This is a C-like model, extended by the fact most pointers are owned by objects that will deallocate them automatically or when needed (at said objects destruction, mostly), and that the life duration of objects are predictable (RAII is your friend, again).

In this model, raw pointers are freely circulating and mostly not dangerous (but if the developer is smart enough, he/she will use references instead whenever possible).

  • raw pointers
  • std::auto_ptr
  • boost::scoped_ptr

Smart Pointed C++ Model

In a code full of smart pointers, the user can hope to ignore the lifetime of objects. The owner is never the user code: It is the smart pointer itself (RAII, again). The problem is that circular references mixed with reference counted smart pointers can be deadly, so you have to deal both with both shared pointers and weak pointers. So you have still ownership to consider (the weak pointer could well point to nothing, even if its advantage over raw pointer is that it can tell you so).

  • boost::shared_ptr
  • boost::weak_ptr

Conclusion

No matter the models I describe, unless exception, receiving a pointer is not receiving its ownership and it is still very important to know who owns who. Even for C++ code heavily using references and/or smart pointers.

Grotto answered 18/9, 2008 at 19:39 Comment(0)
E
21

For me, these 3 kinds cover most of my needs:

shared_ptr - reference-counted, deallocation when the counter reaches zero

weak_ptr - same as above, but it's a 'slave' for a shared_ptr, can't deallocate

auto_ptr - when the creation and deallocation happen inside the same function, or when the object has to be considered one-owner-only ever. When you assign one pointer to another, the second 'steals' the object from the first.

I have my own implementation for these, but they are also available in Boost.

I still pass objects by reference (const whenever possible), in this case the called method must assume the object is alive only during the time of call.

There's another kind of pointer that I use that I call hub_ptr. It's when you have an object that must be accessible from objects nested in it (usually as a virtual base class). This could be solved by passing a weak_ptr to them, but it doesn't have a shared_ptr to itself. As it knows these objects wouldn't live longer than him, it passes a hub_ptr to them (it's just a template wrapper to a regular pointer).

Ellipsis answered 18/9, 2008 at 17:7 Comment(5)
Instead of creating your own pointer class (hub_ptr), why don't you just pass *this to these objects and let them store it as a reference? Since you even acknowledge that the objects will be destroyed at the same time as the owning class, I don't understand the point of jumping through so many hoops.Jahncke
It's basically a design contract to make the things clear. When the child object receives the hub_ptr, it knows that the pointed object won't be destroyed during the child's lifetime, and has no ownership to it. Both the contained and container objects agree to a clear set of rules. If you use a naked pointer, the rules can be documented, but won't be enforced by the compiler and the code.Ellipsis
Also note that you can have #ifdefs to make hub_ptr be typedef'd to a naked pointer in release builds, so the overhead will exist only in the debug build.Ellipsis
Note that the Boost documentation contradicts your description of scoped_ptr. It states that it is noncopyable and that ownership can not be transferred.Spiceberry
@Alec Thomas, You're right. I was thinking about auto_ptr and wrote scoped_ptr. Corrected.Ellipsis
K
10

Don't have shared ownership. If you do, make sure it's only with code you don't control.

That solves 100% of the problems, since it forces you to understand how everything interacts.

Kokura answered 18/9, 2008 at 17:27 Comment(0)
R
2
  • Shared Ownership
  • boost::shared_ptr

When a resource is shared between multiple objects. The boost shared_ptr uses reference counting to make sure the resource is de-allocated when everybody is finsihed.

Retouch answered 18/9, 2008 at 16:37 Comment(0)
F
2

std::tr1::shared_ptr<Blah> is quite often your best bet.

Flickinger answered 18/9, 2008 at 16:38 Comment(2)
shared_ptr is the most common. But there are many more. Each has its own usage pattern and good and bad places to sue. A bit more description would be nice.Retouch
If you're stuck with an older compiler, boost::shared_ptr<blah> is what std::tr1::shared_ptr<blah> is based on. It's a simple enough class that you can probably rip it from Boost and use it even if your compiler isn't supported by the latest version of Boost.Stemware
H
2

From boost, there's also the pointer container library. These are a bit more efficient and easier to use than a standard container of smart pointers, if you'll only be using the objects in the context of their container.

On Windows, there are the COM pointers (IUnknown, IDispatch, and friends), and various smart pointers for handling them (e.g. the ATL's CComPtr and the smart pointers auto-generated by the "import" statement in Visual Studio based on the _com_ptr class).

Hippocampus answered 21/12, 2008 at 12:13 Comment(0)
A
1
  • One Owner
  • boost::scoped_ptr

When you need to allocate memory dynamically but want to be sure it gets deallocated on every exit point of the block.

I find this usefull as it can easily be reseated, and released without ever having to worry about a leak

Aerobic answered 18/9, 2008 at 17:4 Comment(0)
B
1

I don't think I ever was in a position to have shared ownership in my design. In fact, from the top of my head the only valid case I can think of is Flyweight pattern.

Blair answered 18/9, 2008 at 17:13 Comment(0)
M
1

yasper::ptr is a lightweight, boost::shared_ptr like alternative. It works well in my (for now) small project.

In the web page at http://yasper.sourceforge.net/ it's described as follows:

Why write another C++ smart pointer? There already exist several high quality smart pointer implementations for C++, most prominently the Boost pointer pantheon and Loki's SmartPtr. For a good comparison of smart pointer implementations and when their use is appropriate please read Herb Sutter's The New C++: Smart(er) Pointers. In contrast with the expansive features of other libraries, Yasper is a narrowly focused reference counting pointer. It corresponds closely with Boost's shared_ptr and Loki's RefCounted/AllowConversion policies. Yasper allows C++ programmers to forget about memory management without introducing the Boost's large dependencies or having to learn about Loki's complicated policy templates. Philosophy

* small (contained in single header)
* simple (nothing fancy in the code, easy to understand)
* maximum compatibility (drop in replacement for dumb pointers)

The last point can be dangerous, since yasper permits risky (yet useful) actions (such as assignment to raw pointers and manual release) disallowed by other implementations. Be careful, only use those features if you know what you're doing!

Mores answered 21/12, 2008 at 5:18 Comment(0)
S
1

There is another frequently used form of single-transferable-owner, and it is preferable to auto_ptr because it avoids the problems caused by auto_ptr's insane corruption of assignment semantics.

I speak of none other than swap. Any type with a suitable swap function can be conceived of as a smart reference to some content, which it owns until such time as ownership is transferred to another instance of the same type, by swapping them. Each instance retains its identity but gets bound to new content. It's like a safely rebindable reference.

(It's a smart reference rather than a smart pointer because you don't have to explicitly dereference it to get at the content.)

This means that auto_ptr becomes less necessary - it's only needed to fill the gaps where types don't have a good swap function. But all std containers do.

Selfrising answered 21/12, 2008 at 12:29 Comment(2)
Maybe it becomes less necessary (I'd say scoped_ptr makes it less necessary than this), but it's not going away. Having a swap function doesn't help you at all if you allocate something on the heap and somebody throws before you delete it, or you simply forget.Jahncke
That's exactly what I said in the last paragraph.Selfrising
R
0
  • One Owner: Aka delete on Copy
  • std::auto_ptr

When the creator of the object wants to explicitly hand ownership to somebody else. This is also a way documenting in the code I am giving this to you and I am no longer tracking it so make sure you delete it when you are finished.

Retouch answered 18/9, 2008 at 16:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.