dertharino

When To Use Pointers?

Recommended Posts

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).

Share this post


Link to post
Share on other sites

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?

Edited by kop0113

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Edited by dcoolllx
anwser was slightly misleading

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
Share on other sites
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.

 

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites

The default case is to work by value:

void doThing(int num) { num++; }

In this case 'num' is a copy of whatever you pass in when calling the function.

int derp = 1;
doThing(derp);
//derp still equals 1 because it was a copy that got incremented

If you want to be able to modify the original value then you can pass by reference:

void doThingByRef(int& num) { num++; }

Now 'num' is effectively a direct alias of whatever you pass in:

int derp = 1;
doThing(derp);
//derp now equals 2

There's another case to consider... What if the thing you're passing in is large, but you don't want to modify it?

You don't want to do an unnecessary copy, so you may use a reference, but that suggests that you're modifying the item within the function. In this case you could use a const reference:

void doThingByConstRef(const BigThing& thing) { ??? }

This lets you read 'thing' but not modify it (you'll get a compile error).

 

These are the basics of reference semantics. Pointers are similar, but they have some additional properties:

1) A pointer is "nullable". That is, it can be set to 'nulllptr' in order to indicate that it doesn't actually point to anything.

2) A pointer is "reseatable". That is, it can be modified to point at something else.

 

A note about pointers and constness:

int* num; //the pointer can be changed and the int that it points to can be changed
const int* num; //the pointer can be changed but the int that it points to can not
int* const num; //the pointer can not be changed but the int that it points to can be (probably use a reference instead in this case)
const int* const num; //neither the pointer or the pointed-to int can be changed (probably just use a const reference instead)

 

Be careful about returning pointers and references from functions. If the thing that you're referring to is destroyed or moved then you end up with a dangling reference/pointer, and that's no fun at all.

 

So the basic rule of thumb here is:

Use value semantics by default.

Use references when you want references.

Use pointers when you want references that are reseatable and/or nullable.

 

 

Cheers.

Edited by Khatharr

Share this post


Link to post
Share on other sites

My experience of pointer use is perhaps slightly different to some others on this thread, as I work on a fork of Simutrans, which has a very old codebase (1990s), and which uses pointers in a more traditional way: basic pointers are usually used whenever reference semantics are needed (and references used fairly occasionally). "Quickstone" type smart pointers (a variant on the "tombstone" system, the code for the smart pointers being custom written for this specific game) are used in certain special cases where one part of the code cannot easily know whether a pointer to an object in another part of the code has been deleted. Typically, this is used for in-game objects (such as vehicles) which the player may delete at any time. Null pointers (using a preprocessor definition of NULL as 0x000000, as the codebase was written before nullptr was standard) are used frequently for logic operations (e.g., if a player queries a tile of ground that does not have a way such as a road on it, the function that returns a pointer to a way will return NULL, which then has to be checked in the logic for deciding what information to display before attempting to retrieve any data from that object).

This more traditional use of pointers is, I suspect, a little faster but considerably more error-prone than the more modern approach suggested here of using smart pointers in all cases. I do not necessarily recommend that approach in a new project, but it may be useful to know that this is how things were done in the past to put your understanding of the use of pointers into some perspective, and it may well also be useful if you were ever to work on an older codebase where these older techniques are still used.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Announcements

  • Forum Statistics

    • Total Topics
      628375
    • Total Posts
      2982318
  • Similar Content

    • By Kazuma506
      I am trying to recreate the combat system in the game Life is Feudal but make it more complex. The fighting system works by taking in the direction of the mouse movement and if you press the left click it will swing in that direction, though stab, overhead, left (up, down, left right) and right are the only swings that you can do. If you wanted to you could also hold the swing by holding left click so you are able to swing at the perfect moment in the battle. I want to change this so add in more swing directions but I also want to code this from scratch in Unreal. Can anyone give me any pointers or maybe a few snippets of code that work in Unreal that could help me start to implement this type of system?
       
       
    • By rXpSwiss
      Hello,
      I am sending compressed json data from the UE4 client to a C++ server made with boost.
      I am using ZLib to compress and decompress all json but it doesn't work. I am now encoding it in base64 to avoid some issues but that doesn't change a thing.
      I currently stopped trying to send the data and I am writing it in a file from the client and trying to read the file and decompress on the server side.
      When the server is trying to decompress it I get an error from ZLib : zlib error: iostream error
      My question is the following : Did anyone manage to compress and decompress data between a UE4 client and a C++ server ?
      I cannot really configure anything on the server side (because boost has its ZLib compressor) and I don't know what is wrong with the decompression.
      Any idea ?
      rXp
    • By noodleBowl
      I was wondering if someone could explain this to me
      I'm working on using the windows WIC apis to load in textures for DirectX 11. I see that sometimes the WIC Pixel Formats do not directly match a DXGI Format that is used in DirectX. I see that in cases like this the original WIC Pixel Format is converted into a WIC Pixel Format that does directly match a DXGI Format. And doing this conversion is easy, but I do not understand the reason behind 2 of the WIC Pixel Formats that are converted based on Microsoft's guide
      I was wondering if someone could tell me why Microsoft's guide on this topic says that GUID_WICPixelFormat40bppCMYKAlpha should be converted into GUID_WICPixelFormat64bppRGBA and why GUID_WICPixelFormat80bppCMYKAlpha should be converted into GUID_WICPixelFormat64bppRGBA
      In one case I would think that: 
      GUID_WICPixelFormat40bppCMYKAlpha would convert to GUID_WICPixelFormat32bppRGBA and that GUID_WICPixelFormat80bppCMYKAlpha would convert to GUID_WICPixelFormat64bppRGBA, because the black channel (k) values would get readded / "swallowed" into into the CMY channels
      In the second case I would think that:
      GUID_WICPixelFormat40bppCMYKAlpha would convert to GUID_WICPixelFormat64bppRGBA and that GUID_WICPixelFormat80bppCMYKAlpha would convert to GUID_WICPixelFormat128bppRGBA, because the black channel (k) bits would get redistributed amongst the remaining 4 channels (CYMA) and those "new bits" added to those channels would fit in the GUID_WICPixelFormat64bppRGBA and GUID_WICPixelFormat128bppRGBA formats. But also seeing as there is no GUID_WICPixelFormat128bppRGBA format this case is kind of null and void
      I basically do not understand why Microsoft says GUID_WICPixelFormat40bppCMYKAlpha and GUID_WICPixelFormat80bppCMYKAlpha should convert to GUID_WICPixelFormat64bppRGBA in the end
       
    • By HD86
      As far as I know, the size of XMMATRIX must be 64 bytes, which is way too big to be returned by a function. However, DirectXMath functions do return this struct. I suppose this has something to do with the SIMD optimization. Should I return this huge struct from my own functions or should I pass it by a reference or pointer?
      This question will look silly to you if you know how SIMD works, but I don't.
    • By pristondev
      Hey, Im using directx allocate hierarchy from dx9 to use a skinned mesh system.
      one mesh will be only the skeleton with all animations others meshes will be armor, head etc, already skinned with skeleton above. No animation, idle position with skin, thats all I want to use the animation from skeleton to other meshes, so this way I can customize character with different head, armor etc. What I was thinking its copy bone matrices from skeleton mesh to others meshes, but Im a bit confused yet what way I can do this.
       
      Thanks.
  • Popular Now