dertharino

When To Use Pointers?

Recommended Posts

dertharino    121

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
kop0113    2453

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
jpetrie    13159

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
dcoolllx    2

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
dertharino    121

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
Lactose    11471
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.

Share this post


Link to post
Share on other sites
jpetrie    13159
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
Ryan_001    3476
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
dertharino    121
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
Khatharr    8812

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
jamespetts    172

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


  • Similar Content

    • By cozzie
      Hi,
      During the journey of creating my 2nd 3D engine, I'm basing quite some of the approaches on the Game Engine Architecture book (Jason Gregory). I've now arrived on the topic: IO.
      In short, the target platforms for me are PC, XBone and PS4.
      With this main assumption I thought of the following logics/ guidelines to follow:
      - I can assume file/folder structures will work on all 3 platforms, when I use '/' as a folder separator
      -- I will define 1 global with the base folder for the application, the rest will 'inherit' from there
      (which can be anything, independent of the 'mount' or drive)
      -- for now I'll create 1 subfolder with data/files that might need write access, so later on I only have to worry about 1 subfolder (settings, configs etc.).
      - file extensions can be longer than 3 characters (in Linux based FreeBSD on PS4)
      - all class members functions needing to load a file, shouldn't have to now about the file structure of the logical application, so they will all take a full filename string including path
      (combining root + subfolder + filename and separators is then the responsibility of the caller/ calling code)
      - some functions will need to be passed a folder, because contents in that folder need to be read OR I can simply define a small list of defined subfolders (under root/ base), because it won't be more then 5 to 10 folders in total (data/shaders, data/textures, data/objects, data/sound etc.)
      My questions:
      - what do you think about this approach?
      - regarding the last point, which of the 2 options would you apply?
      -- option 2 might work fine but feels a bit 'static', not very flexible (on the other hand, would you actually need flexibility here?)
      Any input is appreciated, as always.
    • By RubenRS
      How do i open an image to use it as Texture2D information without D3DX11CreateShaderResourceViewFromFile? And how it works for different formats like (JPG, PNG, BMP, DDS,  etc.)?
      I have an (512 x 512) image with font letters, also i have the position and texcoord of every letter. The main idea is that i want to obtain the image pixel info, use the position and texcoords to create a new texture with one letter and render it. Or am I wrong in something?
    • By MarcusAseth
      this is super strange...
      I have this function here:
      template<typename TFirst, typename... TArgs> bool util::LogConsole(const TFirst& first, const TArgs&... rest) { std::cout << first << " "; return LogConsole(rest...); } bool util::LogConsole() { std::cout << std::endl; return false; } it's recursive, keep printing "first" and recursively sending the rest, until rest is empty and the overload taking no argument is called, which just print a newline. Ignore the bool return.
      Anyway, now I have this code somewhere else:
      for (auto& E : Entities) { float valx = E->GetPosition().x;//return 751 float valy = E->GetPosition().y;//return 838 LogConsole(valx, valy); } Entities is a vector<unique_ptr<Entity>> which currently only contains the derived from Entity player Paddle, so I access the Base class Entity method called GetPosition, which is the center pivot coordinates of the object.
      I pass the variable to LogConsole which predictably prints:
      But not I try to call LogConsole without those 2 variables inbetween, like this:
      for (auto& E : Entities) { LogConsole(E->GetPosition().x, E->GetPosition().y); } and watch what kind of output I get! :
      ... the y value is an unreasonable value o_o
      How is this even possible?! Since only the second value is being affected, my assumption is that there is some weird interplay with the second template parameter, the variadic argument...but I wasn't able to debug it even stepping trough it line by line...can anyone come with an explanation for this really strange behaviour?
    • By MarcusAseth
      Maybe today I'm confused and I'm missing the obvious...
      I've made this simple struct inside my Utility.h 
      template<typename TFirst, typename TSecond> struct Pair { Pair(TFirst f, TSecond s):First{f},Second{s}{} TFirst First; TSecond Second; }; And I'm using it from App.h (which #include "Utility.h") like so:
      App.h
      Pair<Uint32, Uint32> GetWindowSize(); App.cpp
      Pair<Uint32, Uint32> App::GetWindowSize() { return Pair<Uint32, Uint32>(Width,Height); } And I'm getting a cascade of errors ALL on line 39 (image below).
      Any idea what I am doing wrong this time? x_x

       
      EDIT: please mod delete this topic, I just remembered Utility.h put everything inside namespace util, sorry x_x
    • By noodleBowl
      I've gotten to part in my DirectX 11 project where I need to pass the MVP matrices to my vertex shader. And I'm a little lost when it comes to the use of the constant buffer with the vertex shader
      I understand I need to set up the constant buffer just like any other buffer:
      1. Create a buffer description with the D3D11_BIND_CONSTANT_BUFFER flag 2. Map my matrix data into the constant buffer 3. Use VSSetConstantBuffers to actually use the buffer But I get lost at the VertexShader part, how does my vertex shader know to use this constant buffer when we get to the shader side of things
      In the example I'm following I see they have this as their vertex shader, but I don't understand how the shader knows to use the MatrixBuffer cbuffer. They just use the members directly. What if there was multiple cbuffer declarations like the Microsoft documentation says you could have?
      //Inside vertex shader cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; struct VertexInputType { float4 position : POSITION; float4 color : COLOR; }; struct PixelInputType { float4 position : SV_POSITION; float4 color : COLOR; }; PixelInputType ColorVertexShader(VertexInputType input) { PixelInputType output; // Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the input color for the pixel shader to use. output.color = input.color; return output; }  
  • Popular Now