Advertisement

When To Use Pointers?

Started by June 21, 2017 05:44 PM
11 comments, last by jamespetts 7 years, 5 months ago

I've just moved from C#/Java to C++ and, as you might expect, a big problem I'm facing is understanding when to use pointers (I know that Java and C# use pointers under the hood but I never needed to understand them to program :P). I understand conceptually what pointers are but I can't seem to think of when to actually use them over just plain objects, except for perhaps arrays.

What makes it worse is that while most people on the internet say to avoid pointers when possible, whenever I read source codes for game engines and for games developed in C++, I see pointers everywhere. 

For example, an excerpt from Game Programming Patterns:


class GameObject
{
public:
  int velocity;
  int x, y;

  GameObject(InputComponent* input,
             PhysicsComponent* physics,
             GraphicsComponent* graphics)
  : input_(input),
    physics_(physics),
    graphics_(graphics)
  {}

  void update(World& world, Graphics& graphics)
  {
    input_->update(*this);
    physics_->update(*this, world);
    graphics_->update(*this, graphics);
  }

private:
  InputComponent* input_;
  PhysicsComponent* physics_;
  GraphicsComponent* graphics_;
};

Why shouldn't InputComponent, PhysicsComponent, and GraphicsComponent just be objects?

I guess all I'm asking for is help understanding why and when to use pointers.

Thanks (sorry for all the text).

If you passed in Objects rather than pointers to objects, they would be copies. So if you modify them or get data from them, you would be modifying the copies. The original ones (perhaps a core part of you engine) will remain unchanged. This is obviously an issue if you added (i.e) a new Physics object to the *copy* of the PhysicsComponent, the original PhysicsComponent will remain unchanged causing issues later when you query the original expecting the additional Physics object to be registered.

In C#, I see a lot of inexperienced developers make the same mistake for "boxed" value types like structs. They modify a Vector3 for example, pass that Vector3 into some other class but then modify the original Vector3 again, expecting it to change the Vector3 in the other class. In C# *everything* acts a pointer apart from structs/boxed types.


// C# using a library where Vector3 is a struct, value type
Vector3 v = new Vector3();
player.position = v;
v.x = 99;
// player.position.x is not 99

// C++ (avoiding pointers)
Vector3 v;
player->position = v;
v.x = 99;
// player->position.x is not 99

// C# using a library where Vector3 is a class
Vector3 v = new Vector3();
player.position = v;
v.x = 99;
// player.position.x is 99

// C++ (using pointers)
Vector3 *v = new Vector3();
player->position = v;
v->x = 99;
// player->position->x is 99

Perhaps refer to the following complexity in C# to better understand how C++ handles this situation: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing

When people say do not use pointers in C++... that simply isn't feasible. Perhaps they mean they use smart pointers with references as parameters instead? Either way, unless they only stick to OpenGL's GLuint type system, I cannot see them avoiding pointers when dealing with 3rd party C libraries such as SDL. Would be cool to be proved otherwise. Can anyone point me towards a largish project where this is the case?

http://tinyurl.com/shewonyay - Thanks so much for those who voted on my GF's Competition Cosplay Entry for Cosplayzine. She won! I owe you all beers :)

Mutiny - Open-source C++ Unity re-implementation.
Defile of Eden 2 - FreeBSD and OpenBSD binaries of our latest game.
Advertisement

You use pointers when you want referential semantics. That is, you want to be able to refer to some object without actually needing a complete copy.

I can't speak 100% to the rationale for why pointers are used in that code example you provided, as I didn't write it. I can theorize, however. One reason to use pointers here would be if the components are owned by something else, and the game object just needs to be able to refer to the components it owns.

For example, a typical way to implement a game object system using an "entity and component" approach like this is to have all the actual instances of the PositionComponent objects stored in, say, a PhysicsSystem. This system is responsible for the actual lifetime (creating and destroying) the position components, as well as updating them all in one big cache-friendly batch during the game update.

Individual game objects get pointers to the position component that represents their position, so they can read from it when needed. Pointers enable this design; if the game object just has its own copy of a position component, that copy is distinct from any copies in the position system.

You can implement a game object that stores its position component (et cetera) directly if you want. You don't have to use pointers there. But in the particular example you provided, the pointers are being used to refer to an object that is managed elsewhere, which is in general a common use case for referential semantics.

simply put, a pointer should be used as a memory saving practice. sometimes there are objects that a very large and use a lot of memory. for example in the code above, PhysicComponent could be referring to an entire library of 1000s of lines of code. and a single instance of the Physic Component could be several MBytes. then consider that when you pass the object into another function, that function creates a COPY of that object. therefore with each call to that instance, there could be multiple copies of it at runtime. In come pointers. with a pointer we pass only the address of the Obj to the function instead of its value. now there is only one copy of that instance needed for our code to work at runtime

 

consider the following:


using namespace std;
#include<hugeLibrary.h>
int main()
{
  Library test[100];//an array of objs
	for(int x = 0;x<100;x++){
                             
	Lbrary lib = hugeLibrary();//2Mb of data
     test[x] = lib;
                              }
     //test now is 200Mb                         
	startEngine(test);
  doSomething(test);
  closeEngine(test);//each call results in a Copy of test being created
  
}
  
  or-
  
  
  int main(){
  Library* test[100];//an array of pointers
	for(int x = 0;x<100;x++){
                             
	Lbrary lib* = &(hugeLibrary());//32bits of data
     test[x] = lib;
                              }
     //test now is 3kb                         
	startEngine(test);
  doSomething(test);
  closeEngine(test);//each call results in a Copy of test being created
  
}

PS. this is an Extremely simplified answer. technically each lib would be also be destroyed after each function lost scope.

Ohh, that makes a lot more sense, thanks! On a side note, I was wondering on whether it's better to use smart pointers vs raw pointers when developing games. I know lots of people recommend using smart pointers to avoid the hassle of raw pointers, but when it comes to developing games, would having the additional control over when to free the pointers be worth the trouble?

13 minutes ago, dertharino said:

but when it comes to developing games, would having the additional control over when to free the pointers be worth the trouble?

No.

Hello to all my stalkers.

Advertisement

I saw this image days ago, I liked how didactic it is:
pass-by-reference-vs-pass-by-value-anima

27 minutes ago, dertharino said:

I know lots of people recommend using smart pointers to avoid the hassle of raw pointers, but when it comes to developing games, would having the additional control over when to free the pointers be worth the trouble?

It's important for you to understand how regular, "non-smart" pointers work, so I do encourage you to learn. But once you learn how, it should illustrate just how much more useful smart pointers are when it comes to the actual management of object lifetimes. There are very few cases where "having additional control over when to free the pointers" is worth the trouble, and none of them are applicable to a beginner.

That said, smart pointers are not a magical bullet and I disagree with the common dogma that says a regular pointer should never appear in one's code. Generally I use smart pointers such as std::unique_ptr (I rarely have a concrete need for std::shared_ptr) where I need to use a pointer to manage lifetime. I use a bare pointer where I need a nullable reference and lifetime management isn't involved.

Another way to say that is that systems that own objects might keep smart pointers referring to those objects, but hand out bare pointers to client code if handing out a C++ reference ("Thing&") isn't feasible.

If this seems like too subtle a distinction, then I encourage you to simply use smart pointers until you are more comfortable with the ins and outs of lifetime management in code architecture, and then revisit the topic with your improved understanding and experience at a later time.

 

1 minute ago, jpetrie said:

That said, smart pointers are not a magical bullet and I disagree with the common dogma that says a regular pointer should never appear in one's code. Generally I use smart pointers such as std::unique_ptr (I rarely have a concrete need for std::shared_ptr) where I need to use a pointer to manage lifetime. I use a bare pointer where I need a nullable reference and lifetime management isn't involved.

I was going to post basically the exact same thing and agree completely.

I also use raw pointers in places where the lifetime management is know and handled by something else.  You'll quite commonly find raw pointers in class internals as private members since you know when they are created, when they are accessed, when they are destroyed, and who owns what.  When in doubt use a smart pointer.  Many others will suggest to always use smart pointers, which isn't a terrible thing, but is more a 'rule of thumb' for beginners than a hard and fast rule.

23 minutes ago, jpetrie said:

That said, smart pointers are not a magical bullet and I disagree with the common dogma that says a regular pointer should never appear in one's code. Generally I use smart pointers such as std::unique_ptr (I rarely have a concrete need for std::shared_ptr) where I need to use a pointer to manage lifetime. I use a bare pointer where I need a nullable reference and lifetime management isn't involved.

Another way to say that is that systems that own objects might keep smart pointers referring to those objects, but hand out bare pointers to client code if handing out a C++ reference ("Thing&") isn't feasible.

That makes a lot of sense too. Thanks again!

 

32 minutes ago, felipefsdev said:

I saw this image days ago, I liked how didactic it is:
pass-by-reference-vs-pass-by-value-anima

Haha this is a really good graphic. Thanks for that!

This topic is closed to new replies.

Advertisement