Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Within the scope of developing games in C++, what are your preferred patterns in regard to use of pointers (be it none, raw, scoped, shared, or otherwise in between smart and dumb)?

You might consider

  • object ownership
  • ease of use
  • copy policy
  • overhead
  • cyclic references
  • target platform
  • use with containers
share|improve this question

9 Answers 9

After having tried various approaches, today I find myself in alignment with the Google C++ Style Guide:

If you actually need pointer semantics, scoped_ptr is great. You should only use std::tr1::shared_ptr under very specific conditions, such as when objects need to be held by STL containers. You should never use auto_ptr. [...]

Generally speaking, we prefer that we design code with clear object ownership. The clearest object ownership is obtained by using an object directly as a field or local variable, without using pointers at all. [..]

Although they are not recommended, reference counted pointers are sometimes the simplest and most elegant way to solve a problem.

share|improve this answer
8  
Today, you might want to use std::unique_ptr instead of scoped_ptr. –  Klaim Dec 9 '11 at 14:30

I also follow the "strong ownership" train of thought. I like to clearly delineate that "this class owns this member" when its appropriate.

I rarely use shared_ptr. If I do, I make liberal use of weak_ptr whenever I can so I can treat it like a handle to the object instead of increasing the reference count.

I use scoped_ptr all over the place. It shows obvious ownership. The only reason I don't just make objects like that a member is because you can forward declare them if they're in a scoped_ptr.

If I need a list of objects, I use ptr_vector. It's more efficient and has fewer side effects than using vector<shared_ptr>. I think you might not be able to forward declare the type in the ptr_vector (it's been a while), but the semantics of it make it worth it in my opinion. Basically if you remove an object from the list it gets deleted automatically. This also shows obvious ownership.

If I need reference to something, I try to make it a reference instead of a naked pointer. Sometimes this isn't practical (i.e. any time you need a reference after the object is constructed). Either way, references show obviously that you don't own the object, and if you're following shared pointer semantics everywhere else then naked pointers generally don't cause any additional confusion (especially if you follow a "no manual deletes" rule).

With this method, one iPhone game I worked on was able to only have a single delete call, and that was in the Obj-C to C++ bridge I wrote.

Generally I'm of the opinion that memory management is too important to leave to humans. If you can automate deletion, you should. If the overhead from shared_ptr is too expensive at run time (assuming you turned off threading support, etc), you probably should be using something else (i.e. a bucket pattern) to get your dynamic allocations down.

share|improve this answer
1  
Excellent summary. Do you actually mean shared_ptr as opposed to your mentioning of smart_ptr? –  jmp97 Aug 11 '10 at 16:36
    
Yes, I meant shared_ptr. I'll fix that. –  Tetrad Aug 11 '10 at 17:35

Use the right tool for the job.

If your program can throw exceptions ensure your code is exception aware. Using smart pointers, RAII, and avoiding 2 phase construction are good starting points.

If you have cyclic references with no clear ownership semantics you can consider using a garbage collection library or refactoring your design.

Good libraries will allow you to code to the concept not the type so it shouldn't matter in most cases which kind of pointer you are using beyond resource management issues.

If you're working in a multi-threaded environment, make sure you understand if your object is potentially shared across threads. One of the main reasons to consider using boost::shared_ptr or std::tr1::shared_ptr is because it uses a thread-safe reference count.

If you're worried about the separate allocation of the reference counts there are many ways around this. Using the boost::shared_ptr library you can pool allocate the reference counters or use boost::make_shared (my preference) which allocates the object and the reference count in a single allocation thereby alleviating most cache miss concerns people have. You can avoid the performance hit of updating the reference count in performance critical code by holding a reference to the object at the topmost level and passing around direct references to the object.

If you need shared ownership but don't want to pay the cost of reference counting or garbage collection consider using immutable objects or a copy on write idiom.

Bear in mind that far and away your biggest performance wins are going to be at an architecture level, followed by an algorithm level, and while these low level concerns are very important they should be tackled only after you've addressed the major issues. If you're dealing with performance issues at the level of cache misses then you have a whole host of issues that you also have to be aware of like false sharing which have nothing to do with pointers per say.

If you're using smart pointers just to share resources like textures or models consider a more specialized library like Boost.Flyweight.

Once the new standard becomes adopted move semantics, rvalue references, and perfect forwarding will make working with expensive objects and containers much easier and more efficient. Until then don't store pointers with destructive copy semantics, such as auto_ptr or unique_ptr, in a Container (the standard concept). Consider using the Boost.Pointer Container library or storing shared ownership smart pointers in Containers. In performance critical code you can consider avoiding both of these in favor of intrusive containers such as those in Boost.Intrusive.

The target platform shouldn't really influence your decision too much. Embedded devices, smart phones, dumb phones, PCs, and consoles can all run the code just fine. Project requirements such as strict memory budgets or no dynamic allocation ever/after load are more valid concerns and should influence your choices.

share|improve this answer
3  
Exception handling on the consoles can be a bit dodgy -- the XDK in particular is sort of exception-hostile. –  Crashworks Aug 11 '10 at 21:32
1  
Target platform really should influence your design. The hardware that transforms your data can sometimes have large influences on your source-code. The PS3-architecture is a concrete example where you really need to take the hardware into designing your resource and memory-management as well as your renderer. –  Simon Aug 12 '10 at 16:38
    
I disagree only slightly, specifically with regard to GC. Most of the time, cyclic references are not a problem for reference counted schemes. Generally these cyclic ownership problems come up because people didn't think properly about the ownership of objects. Just because an object needs to point to something, doesn't mean it should own that pointer. The commonly cited example is back pointers in trees, but the parent to the pointer in a tree can safely be a raw pointer without sacrificing safety. –  Tim Seguine Sep 15 '13 at 13:06

If you're using C++0x, use std::unique_ptr<T>.

It has no performance overhead, unlike std::shared_ptr<T> which has reference counting overhead. A unique_ptr owns its pointer, and you can transfer ownership around with C++0x's move semantics. You can't copy them - only move them.

It can also be used in containers, e.g. std::vector<std::unique_ptr<T>>, which is binary-compatible and identical in performance to std::vector<T*>, but will not leak memory if you erase elements or clear the vector. This also has better compatibility with STL algorithms than ptr_vector.

IMO for a lot of purposes this is an ideal container: random access, exception safe, prevents memory leaks, low overhead for vector reallocation (just shuffles around pointers behind the scenes). Very useful for many purposes.

share|improve this answer

It's good practice to document which classes owns what pointers. Preferably, you just use normal objects, and no pointers whenever you can.

However, when you need to keep track of resources, passing pointers is the only option. There are some cases:

  • You get the pointer from somewhere else, but do not manage it: just use a normal pointer and document it so that no coder after you tries to delete it.
  • You get the pointer from somewhere else, and you keep track of it: use a scoped_ptr.
  • You get the pointer from somewhere else, and you keep track of it but it needs a special method to delete it: use shared_ptr with a custom delete method.
  • You need the pointer in an STL container: it will be copied around so you need boost::shared_ptr.
  • Many classes share the pointer, and it's not clear who will delete it: shared_ptr (the case above is actually a special case of this point).
  • You create the pointer yourself and only you need it: if you really can't use a normal object: scoped_ptr.
  • You create the pointer and will share it with other classes: shared_ptr.
  • You create the pointer and pass it: use a normal pointer and document your interface so that the new owner knows that he should manage the resource himself!

I think that pretty much covers how I manage my resources right now. The memory cost of a pointer like shared_ptr is generally twice the memory cost of a normal pointer. I don't think that this overhead is too big, but if you are low on resources you should consider designing your game to reduce the number of smart pointers. On other cases I just design to good principles like the bullets above and the profiler will tell me where I will need more speed.

share|improve this answer

When it comes to boost's pointers specifically, I think that they should be avoided as long as their implementation is not exactly what you need. They do come at a cost that is larger than anyone would initially expect. They provide an interface that allows you to skip vital and important parts of your memory and resourcement management.

When it comes to any software development I think that it is important to think about your data. It is very important how your data is represented in memory. The reason for this is that CPU-speed has been increasing at a much greater rate than memory-access time. This often makes the memory-caches the main bottleneck of most modern computer games. By having your data aligned linearly in memory according to access order is much friendlier to the cache. This kind of solutions often lead to cleaner designs, simpler code and definetly code that is more easy to debug. Smart pointers easily lead to frequent dynamic memory allocations of resources, this causes them to be scattered all over the memory.

This is not a premature optimization, it's a healthy decision that can and should be taken as early as possible. It's a question of architectural understanding of the hardware that your software will run on and it is important.

Edit: There are a few things to consider regarding the performance of shared-pointers:

  • The reference counter is heap allocated.
  • If you use thread-safety enabled, reference counting is done via interlocked operations.
  • Passing the pointer by value modifies reference count, which means interlocked operations most likely using random access in memory (locks + likely cache miss).
share|improve this answer
2  
You lost me at 'avoided at all costs.' Then you go on to describe a type of optimization that is seldom a concern for real-world games. Most game development is characterized by development problems (delays, bugs, playability, etc) not by a lack of CPU cache performance. So I strongly disagree with the idea that this advice isn't a premature optimization. –  kevin42 Aug 11 '10 at 15:39
2  
I have to agree with early design of data layout. It is important to get any performance out of a modern console / mobile device and is something that should never be over-looked. –  Olly Aug 11 '10 at 16:10
1  
This is an issue that I've seen at one of the AAA-studios that I've been working at. You can also listen to the Head Architect at Insomniac Games, Mike Acton. I'm not saying that boost is a bad library, it's not just well suited for high-performant games. –  Simon Aug 12 '10 at 5:47
1  
@kevin42: Cache coherency is probably the main source of low-level optimizations in game development today. @Simon: Most shared_ptr implementations avoid locks on any platform that supports compare-and-swap, which includes Linux and Windows PCs, and I believe includes the Xbox. –  user744 Aug 12 '10 at 8:57
1  
@Joe Wreschnig: That's true, the cache-miss is still most likely though causing any initialization of a shared-pointer (copy, create from weak pointer etc). An L2 cache-miss on modern PC's is like 200 cycles and on the PPC (xbox360/ps3) it is higher. With an intense game you might have up to 1000 game objects, given that each game object can have quite a few resources we're looking at issues where their scaling is a major concern. This will likely cause issues at the end of a development cycle (when you will hit the high amount of game objects). –  Simon Aug 12 '10 at 13:07

I tend to use smart pointers everywhere. I am not sure if this is a totally good idea, but I am lazy, and I cannot see any real downside [except if I wanted to do some C-style pointer arithmetic]. I use boost::shared_ptr because I know I can copy it around - if two entities share an image, then if one dies the other shouldn't lose the image too.

The downside of this is if one object deletes something it points to and owns, but something else is also pointing to it, then it isn't deleted.

share|improve this answer
1  
I have been using share_ptr almost everywhere, too - but today I try to think whether or not I actually need shared ownership for some piece of data. If not, it might be reasonable to make that data a non-pointer member to the parent data structure. I find that clear ownership simplifies designs. –  jmp97 Aug 11 '10 at 11:24

The benefits of memory management and documentation provided by good smart pointers mean I use them regularly. However when the profiler pipes up and tells me a particuarly usage is costing me, I'll revert back to more neolithic pointer management.

share|improve this answer

I'm old, oldskool, and a cycle counter. In my own work I use raw pointers and no dynamic allocations at runtime (except the pools themselves). Everything is pooled, and ownership is very strict and never transferable, if really needed I write a custom small block allocator. I make sure that there is a state during game for every pool to clear itself. When things get hairyI do wrap objects in handles so I can relocate them, but I'd rather not. Containers are custom and extremely bare bones. I also don't reuse code.
While I would never argue the virtue of all the smart pointers and containers and iterators and whatnot, I am known for being able to code extremely fast (and reasonably reliable - although it's not advisable for others to jump into my code for somewhat obvious reasons, like heart-attacks and perpetual nightmares).

At work, of course, all is different, unless I am prototyping, which I thankfully get to do a lot.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.