I am having a hard time wrapping around knowing when to use pointers vs references. My question is: in Java/C# you can pass an object as an argument to a function and then assign this argument to an internal class variable so that you can use it outside the scope of the method later on. However, in C++ I am not sure how to achieve the same thing. If I pass by reference, I can only use it within the scope of that method. If I assign the reference to an internal variable of the same type, changes on one don't affect the other. I cannot declare an uninitialized reference either (maybe through constructor). The only solutions I have found are to either pass in the reference every time I need to work with it (as an argument) or pass in a pointer instead once (e.g. through a constructor) and convert it to a reference every time I need to use it outside the scope.
Here is an example method I have for testing this:
- initially the value referenced by get and setValue is set to zero.
- I call Controller2.initialize(Controller &controller, Controller *controllerPtr)
- I call Controller2::process(Controller &controller)
The output is shown after the code blocks below
#include "Controller2.h"
Controller2::Controller2()
{
}
void Controller2::initialize(Controller &controller, Controller *controllerPtr)
{
_controller = controller;
_controllerPtr = controllerPtr;
Controller &controllerRef = *_controllerPtr;
controller.setValue(5);
Serial.println("");
Serial.print("_Controller in initialize(): ");
Serial.print(_controller.getValue());
Serial.print(" Controller in initialize(): ");
Serial.print(controller.getValue());
Serial.print(" Controller Ptr in initialize(): ");
Serial.print(controllerRef.getValue());
Serial.println();
}
void Controller2::process(Controller &controller)
{
Serial.println("");
Serial.print("_Controller in process(): ");
Serial.print(_controller.getValue());
Serial.print(" Controller in process(): ");
Serial.print(controller.getValue());
Controller &controllerRef = *_controllerPtr;
Serial.print(" Controller Ptr in process(): ");
Serial.print(controllerRef.getValue());
Serial.println();
}
Controller2.h:
#include "Arduino.h"
#include "Controller.h"
#ifndef Controller2_h
#define Controller2_h
class Controller2
{
public:
Controller2();
void initialize(Controller &controller, Controller* controllerPtr);
void manage();
void process(Controller &controller);
private:
Controller _controller;
Controller* _controllerPtr;
};
#endif
Controller Class:
#include "Controller.h"
Controller::Controller()
{
}
void Controller::initialize()
{
}
void Controller::setValue(int val)
{
value = val;
}
int Controller::getValue()
{
return value;
}
Controller.h:
#include "Arduino.h"
#ifndef Controller_h
#define Controller_h
class Controller
{
public:
Controller();
void initialize();
void manage();
void setValue(int val);
int getValue();
private:
int value = 0;
};
#endif
And the main class:
#include <Arduino.h>
#include <Controller.h>
#include <Controller2.h>
Controller controller;
Controller2 controller2;
void setup()
{
Serial.begin(115200);
Serial.println("");
Serial.print("Controller initial: ");
Serial.print(controller.getValue());
Serial.println();
controller2.initialize(controller, &controller);
controller2.process(controller);
}
void loop()
{
}
The output results in:
Controller initial: 0
_Controller in initialize(): 0 Controller in initialize(): 5 Controller Ptr in initialize(): 5
_Controller in process(): 0 Controller in process(): 5 Controller Ptr in process(): 5
Is this correct or am I missing something here?
The truth is that C++ references behave in many ways like pointers, without the pointer-specific syntax. For both pointers and references you have the pointer/reference-to-an-object and then you have the object itself, and the lifetime of the pointer/reference can differ from the lifetime of the object that it points-to/references, so in cases where the pointer/reference outlives the object, you have to be very careful not to dereference the pointer/reference after the object has been destroyed, or else you'll invoke undefined behavior and your program won't behave well.
So for example this is valid:
... and behaves much the same as the pointer-based implementation:
... the main difference being that in the reference-based implementation, there is no (legal) way for the user to pass in a NULL reference to the Controller2 constructor, therefore your code doesn't have to worry about checking _controllerRef to see if it's NULL, since the language guarantees it won't be NULL (or to be more specific, it says that if the reference is NULL, then the program is already broken beyond repair, so you can assume it isn't NULL).
In both cases, passing a raw-pointer/reference to an external object is a bit risky, so unless you have some other way to guarantee that the pointed-to/referenced object will outlive any possible dereferencing of _controllerRef, you might be better off either making a private copy of the object, or if that isn't practical, using something like a shared_ptr or unique_ptr instead to guarantee that the referenced object won't be destroyed until after you've stopped holding any references to it.