When to use references and when to use smart pointers

Started by
7 comments, last by Heelp 7 years, 4 months ago

I have a pretty clear understanding of how pointers, smart pointers and references work, but I'm having a really hard time wrapping my head around when I should use each of them. I've googled it a bit, but I still feel that I need some more clarity.

I very recently started using smart pointers and had been using raw pointers for most things before. Now I understand that I should avoid raw pointers whenever I'm not forced to it by the API I'm using (I'm programming against SDL, which uses raw pointers for its own types). So now I'm trying to figure out when to use references, when to use unique_ptr, when to use shared_ptr and when to use weak_ptr. I'm also not very sure about when to create new objects on the heap and when to create them on the stack/static space, although I feel that I'm starting to understand that better now. I come from Java, where I didn't really have to think about any of this.

I guess I'll try to give some concrete examples of situations in which I am unsure.

Passing to a function that won't store the object

Let's say that I have have a member function of a collider class like this:


double BoxCollider::getXCollision(Collider & collider)
{
    //First check which type of Collider the argument is (BoxCollider or CircleCollider). Should I use dynamic_cast for this? Seems a bit tricky on references.
    //Check if and how much the two colliders
}

Should I be using a reference here? As it is right now, each game object has a unique_ptr to its own collider, which is allocated on the heap before the game object is created. Is it a good idea to use references to objects that are heap allocated and stored as a unique_ptr? My question here is: should I ALWAYS use references as arguments to functions that will process the argument but not store it, if I expect it to not be null (although I can never guarantee it to not be null)? Or are there times when I should use some kind of pointer for this? Passing unique_ptr's are obviously not possible. Also, in this particular case, I need to check if the collider is a CircleCollider or a BoxCollider, which seems more tricky on references than on pointers. Should I still use a reference and use a nestled try/catch instead of a if/else to check the type?

Passing to a function or constructor that will store the object

Let's take a look at my GameObject constructor:


GameObject::GameObject(double xPosition, double yPosition, std::unique_ptr<GraphicsComponent> graphics, std::unique_ptr<PhysicsComponent> physics, std::unique_ptr<InputComponent> input) :
graphics{ std::move(graphics) }, physics{ std::move(physics) }, input{ std::move(input) }, xPosition{ xPosition }, yPosition{ yPosition }
{
}

As you can see, I'm using the component pattern for GraphicsComponent, PhysicsComponent and InputComponent. Right now I'm using unique_ptr to refer to them as they are all constructed on the heap before they are sent to GameObject's constructor. Does this make sense?

I have another example: GameObject has a function called setWorld that tells the GameObject which world it is in. Currently that function takes in a weak_ptr because I don't want the GameObject to own the world as that could cause problems.


void GameObject::setWorld(std::weak_ptr<World> world)
{
	this->world = world;
}

I realized though that the only thing that should own the World object is my main Game object, so would it make more sense to use a unique_ptr to store it in the Game class and a reference to store it in the GameObject class? Or should I even store World as a plain object in Game as the world is loaded in Game? Like this:


class Game
{
    public:
        Game()
        ~Game()
        //Other stuff
    private:
        World world;
}

How do I know if I want to store something as a pointer, as a reference or as a plain object?

I was sure that there were something else regarding references that I was uncertain about, but maybe my brain sorted it out. I'll write it here if I come up with more questions. Would be thankful for any help and clarification!

Advertisement

Passing to a function that won't store the object - pass a const reference if you're not modifying the object. Pass a reference if you are modifying the object. Passing a reference makes it clear that ownership can't change.

(Regarding your comment - ideally you shouldn't use dynamic_cast for anything. Handle type-based differences via virtual functions and overloads. Consider the visitor pattern when you need to have behaviour that is dependent on 2 types rather than just 1.)

Is it a good idea to use references to objects that are heap allocated and stored as a unique_ptr? Sure, providing the functions you pass them to don't make a habit of grabbing their own pointer to the object and holding onto it. That's as much about code discipline as anything else, because unique_ptr doesn't save you from this either. (Just call 'get' on it.)

seems more tricky on references than on pointers - No, references are just pointers with different syntax.

Passing to a function or constructor that will store the object - use unique_ptr if you want to transfer ownership, shared_ptr if you want to share it.

so would it make more sense to use a unique_ptr to store it in the Game class and a reference to store it in the GameObject class? Given that you have a setWorld function, you can't use a reference, as references can't be assigned or changed after construction time. If, however, you can pass it in to the constructor, then a reference might be a good idea, if all game objects always have to exist within a world.

should I even store World as a plain object in Game as the world is loaded in Game - no, because there you're creating a new world for every single game object. Probably not what you intend.

How do I know if I want to store something as a pointer, as a reference or as a plain object? Use pointers and references to represent associations where possible, and use 'plain' objects to represent composition, where possible. Use a pointer if the 2 associated objects have unrelated lifetimes, or if the association may be altered or removed later. Otherwise, consider using a reference.

Smart pointers are best thought about in terms of ownership. What code is responsible for creating the object, what code is responsible for destroying the object, and what code is just using an object in the middle of its lifetime?

  • unique_ptr means "I create the object and I destroy the object, I'm the only one responsible for its lifetime".
  • shared_ptr means "Someone creates this and we aren't really sure who cleans it up, but it will be cleaned up when everyone is done with it". While it can work out well compared to the source/sink model that uses a creating function and destroying function, it also tends to be overused and misused. Often it is a bad excuse for poor memory management. It creates a hidden dependency, which is the same core problem of global variables and singletons. The biggest reason for using shared_ptr is when object lifetimes are truly ambiguous, which is rarely the case in games.
  • weak_ptr means "I don't own this, and it might go away eventually, so there is probably something here that needs to be worked with, but maybe not." There are a few cases where weak_ptr makes sense such as cyclic structures, but almost always it means the code has terrible design flaws related to object lifetimes. Like above it has a hidden dependency on the shared_ptr that owns it, and in games object lifetimes should be fully understood.
  • Raw pointers mean "Here is a thing that might be empty, use it for a moment." Don't hold on to raw pointers, use the thing and be done with it.
  • References mean "Here is a thing that never can never be empty, use it for a moment." Don't hold on to references, use the thing and be done with it.

That should provide some context for Kylotan's answers.

Going through each of them myself:

> Passing to a function that won't store it

Pass either a reference or a raw pointer. References work with polymorphic behavior, although as you mention some operations like dynamic_cast require some trickery. On that topic, dynamic_cast to anything other than a base interface tends to be a symptom of a design flaw, so you should consider why you are doing that; dynamic_cast to ensure it matches a parent interface tends to be fast, but dynamic_cast to an intermediate level or leaf class tends to mean you are violating SOLID principles and depending on concrete implementation details.

In your example I would either pass a reference if I was absolutely certain it could never be null, or I would pass a pointer if there was a chance it could be null and test against null in the code.

> passing to something that will store the object

Storing the object means ownership. If it takes ownership completely then it should be a unique_ptr. If it doesn't take ownership completely you should do some serious soul-searching and discussion with any teammates you've got to ask yourself why it is taking only partial ownership, and what that really means. If you discuss it and decide there are very good reasons for you to be storing something long-term without actually owning it, then either use a raw pointer if you can guarantee it will never outlive the object, or a shared_ptr or weak_ptr if you absolutely must.

In the case of the components having a link to the object that contains them, that is not ownership. I would build the design so that components absolutely cannot outlive the objects that contain them, and make it into a raw pointer.

In your example of the game objects referencing the world they are in, that is also not ownership. Since objects exist in the world, a raw pointer to the world they are registered to would work. If someone makes a call that removes them from the world, it should also reset that pointer. When they are placed back in the world, the pointer should be set to point to the current world it is in.

Managing the lifetimes of objects is a critical aspect of game development. Do it wrong and eventually your game will leak resources and run out of space or follow bad pointers. The bigger the project the more critical it is that lifetimes are managed in a consistent and correct manner.

Thanks for the helpful answers!

When I was reading about smart pointers I got the impression that good practice was to avoid raw pointers like the plague, but I guess I don't have to be too afraid to use them as long as I never use the new or delete keywords (and make sure to check so that they are not null when necessary). Another mental problem that I have is that I have some kind of obsession with wanting to make things thread safe when I really shouldn't have to worry about it... The game that I'm making currently only runs on one thread, but for some reason I still obsess over the possibility that a reference would suddenly become invalid in the middle of using it. Guess I'll just have to stop worrying and instead think about it for the specific codes that need thread safety if/when I decide to use multiple threads.

(Regarding your comment - ideally you shouldn't use dynamic_cast for anything. Handle type-based differences via virtual functions and overloads. Consider the visitor pattern when you need to have behaviour that is dependent on 2 types rather than just 1.)

On that topic, dynamic_cast to anything other than a base interface tends to be a symptom of a design flaw, so you should consider why you are doing that; dynamic_cast to ensure it matches a parent interface tends to be fast, but dynamic_cast to an intermediate level or leaf class tends to mean you are violating SOLID principles and depending on concrete implementation details.

It did feel like pretty bad design, but I really didn't know how to do it in a better way... I didn't know about the visitor pattern before, but I'll try it out!

should I even store World as a plain object in Game as the world is loaded in Game - no, because there you're creating a new world for every single game object. Probably not what you intend.

I think you misunderstood what I meant here. I meant that I would store the World object as a plain object in Game, created on the stack, instead of a unique_ptr. Then I would pass references of that object to the GameObjects. But since they will be stored I guess I'll use raw pointers in GameObject instead of references. Although the game objects should never outlive the world anyway so it technically shouldn't matter if they store a pointer or a reference. Or I'll try to come up with a way (not involving static/global) to not store a pointer/reference in the GameObject class, but that could result in very cluttered parameter passing...

I got the impression that good practice was to avoid raw pointers like the plague, but I guess I don't have to be too afraid to use them as long as I never use the new or delete keywords (and make sure to check so that they are not null when necessary).

Don't use raw pointers for ownership. They can be used as nullable references as long as you're managing lifetimes correctly (that is, the object pointed to should outlive the pointer). Lifetime management is the same for references and pointers when used in this way.

should I even store World as a plain object in Game as the world is loaded in Game - no, because there you're creating a new world for every single game object. Probably not what you intend.

I think you misunderstood what I meant here. I meant that I would store the World object as a plain object in Game, created on the stack, instead of a unique_ptr. Then I would pass references of that object to the GameObjects. But since they will be stored I guess I'll use raw pointers in GameObject instead of references. Although the game objects should never outlive the world anyway so it technically shouldn't matter if they store a pointer or a reference. Or I'll try to come up with a way (not involving static/global) to not store a pointer/reference in the GameObject class, but that could result in very cluttered parameter passing...

Ideally you should store things by value, especially high-level constructs that have long lifetimes and are referred to a lot. Regardless of the storage type you can hand out references or pointers to the object. Since games are often driven by some kind of state machine you can usually store persistent data at the same level (or within the same object) as the state machine itself and then hand a reference to that object into the states as they're created.

The stack/heap distinction only applies to local variables in this sense. You can have objects declared by value and yet residing on the heap:


struct Foo {
  Foo(int startVal) : val(startVal) {}
  int val;
};

struct Bar {
  Bar() : byVal(42), byPtr(std::make_unique<Foo>(10)) {}
  Foo byVal;
  std::unique_ptr<Foo> byPtr;
};

int main() {
  //a is on the stack a.byVal and a.byPtr are within a (on the stack) a.byPtr points to a heap allocation
  Bar a;
  //the allocation pointed to by b is on the heap and contains b.byVal and b.byPtr. b.byPtr points to an additional heap allocation
  auto b = std::make_unique<Bar>();
}

If you encapsulate the entire project within a Game class with a run() function then you can avoid using the stack for these high-level objects with something like:


int main() {
  std::make_unique<Game>()->run();
}
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

I think you misunderstood what I meant here.

Yep, sorry. I misread the Game class as the GameObject class.

The biggest reason for using shared_ptr is when object lifetimes are truly ambiguous, which is rarely the case in games.

frob, then what do I do if I have std::vector<GameObject*> gameObjects that holds all my objects( it's in my SinglePlayer class), and I need to pass a pointer to that vector to my Player class because I need to access all gameObjects from my player class in order to know who I killed? I need shared_ptr, right? How can I implement this with unique_ptr? ( I normally use raw pointers, but some memory leaks are starting to mess up my game nowadays and I decided to try and do some fancy C++11 stuff, thanks for reading.)

> I have a std::vector<> that holds all my objects (it's in my SinglePlayer class)

Always remember the Single Responsibility Principle. A class or structure should have exactly one responsibility.

I'm not sure what responsibilities your SinglePlayer class has. The name doesn't sound like a single responsibility, especially if it acts as the container of all game objects. I'd consider managing the existence of game objects to be one task, make an object that has the single responsibility of containing all game objects, including their creation and destruction.

With a single point of ownership you know that there will only ever be one owner, only one source for creating and destroying objects. If something keeps a pointer around they need to make sure it is only accessed during the time window where it is guaranteed to still exist. Alternative solutions are to use a handle to an object which can be validated, or perhaps a handle to a proxy object which can be replaced with a generic placeholder object in the event someone uses an object after it is released, or if you are set on using smart pointers, make sure the class only exposes weak_ptr to other systems.

Generally I prefer a proxy class, since the placeholder object can work perfectly with an invisible non-responsive object in a final release of the game and can generate all kinds of warnings and alerts in debug builds whenever it gets used.

A pattern used in many large systems is a Store, Proxy, Cache, and Loader. The Store is the interface to the rest of the game that lets you find objects, request their creation, and request their destruction. The Proxy is an object that can immediately be returned while assets are streamed and loaded, and other systems know how to handle the proxy. (A graphics renderer should know not to render an empty proxy mesh, an audio player should know not to play an empty proxy audio clip.) The Cache decides what is a live object and what is dead, replacing Proxy objects with their real objects, with an unloaded but still live object, or with a dead placeholder object as appropriate. And finally the Loader fetches resources from wherever they need to live, which can mean a single file, a part of a file, many files for meshes and textures and animation clips, or even load across networks or other storage systems. Unfortunately these tend to be a little large and too complex for small games, but as games grow the pattern is everywhere.

> I need to pass a pointer to that vector to my player class because I need access to all gameObjects from my player class in order to know who I killed

Why? That seems horribly inefficient.

Why does your player need to know about EVERY game object? Why does a player need to know about every spawn point, every building, every tree, every doorway, every bridge, every collectible, every game object in the entire world, just to know what character had their life drop to zero?

Do you check the entire world every day for every human, every animal, every building, to see if someone you know is dead, or if a pet has died, or if a building you remember has been demolished? Or do you blissfully ignore it until you get a notice that the event has happened?

Generally the preferred approach to events like "player died" is a listener pattern, or an event bus or message system, where things can register for events like "player died". Then your player object can register for the events they are listening for. The event could pass along a raw pointer to the player that was killed --- it is a raw pointer so there is no ownership implied, it is only for immediate use --- and the player object can do whatever processing it needs. The event will transmit all the details that are needed, the pointer to the character that died, any relevant information about how the character died, weapons used, players getting credit, and so on. Events are only sent to interested listeners so there is no wasted processing of testing against every possible thing that might be interested, instead the code can iterate across the list of listeners and send the event to each one.

With that system you can have many things listening for the event, or perhaps few listeners or even zero listeners. An achievement system may be listening for what player scores the first kill. A scoring system may be listening so it can tabulate which team and which player has the most kills. An audio system may be listening so it can figure out the position of the opponent and play a positional audio sound. A cleanup timer may be listening to know that the dead player needs to be removed from the World after time expires. A logger may listen for the event and make a log entry. A debugging tool may be listening for the event so it can pause with a breakpoint. Most events have few event listeners, but some game events are extremely popular as attachment points for all kinds of game services and systems.

Generally there is also a Simulator or World or other container that has all the game objects placed in the world. (Many game engines allow for other game objects that are not part of the simulator or world, letting them be created for additional purposes.) The World or Simulator generally have a spatial hierarchy allowing searches and queries. If someone fires a shot, it shouldn't be the Weapon base class that searches the World, instead it should ask the World for a collection of all game objects that intersect the line segment, or the first game object that intersects the line segment. If someone were to drop a grenade, again the World would be asked for all items that can be damaged by grenade within the grenade's view. The World would then be responsible for handling impenetrable walls, occluded objects, and other World-related details.

Exactly how deep down the rabbit hole you want to go is up to you. Does the weapon class figure out its range and process it's effects? Or does it trigger an event that a weapon was fired and allow a different system to track the bullets or spells over time? Do those effects also broadcast events, or are the effects handled by that system instead? Different game designs have different reasons why this may be a good or bad design. Usually large games will prefer many events so they can be intercepted for many reasons, simpler games tend to prefer fewer events and favor direct actions and more direct calls to other systems.

> I normally use raw pointers, but some memory leaks are starting to mess up my game

Smart pointers can help mask the symptoms of memory leaks, but the underlying problem is still there. You will still need to fix the problem of not properly managing object lifetimes. Smart pointers don't fix the problem, they only delay it for a while. Smart pointers are still quite likely to have links to other items that are also smart pointers, links and cycles and similar issues still exist, and eventually you get exactly the same memory leak or resource link problems you had before.

//Moved to different post, it's kind of off-topic.

This topic is closed to new replies.

Advertisement