Using Pointers

So I’m a C++ beginner, and I’m just starting to understand some concepts. In order to try to understand how pointers work, I “theorized” (made it up to see if it would work) this:

controller *Controller;
void usercontrol(void) {
  // User control code here, inside the loop
  while (1) {
    // This is the main execution loop for the user control program.
    // Each time through the loop your program should update motor + servo
    // values based on feedback from the joysticks.
      float throttle = Controller -> Axis3.value();
      float turn = Controller -> Axis4.value(); //single stick drive
      float lpct = throttle + turn;
      float rpct = throttle - turn;
      L.spin(fwd, (lpct), pct);
      R.spin(fwd, (rpct), pct);
    // ........................................................................
    // Insert user code here. This is where you use the joystick values to
    // update your motors, etc.
    // ........................................................................

    wait(20, msec); // Sleep the task for a short amount of time to
                    // prevent wasted resources.
  }
}

Would this (theoretically) work? If so, what differences would there be from normal usage? I’m not going to use this (and nobody should) but I’m just curious to see if it would work.

(mods, I made an error in my original post, so I’m reposting with the correct code)

1 Like

You’re on the overall right track, with a few key things to note.

You’ve declared your Controller variable, but not assigned it to anything, thus right now it’s (/should be) null, which when you go to run will cause things to go badly. So, you need to make sure to initialize the Controller, usually with something like:

controller *Controller = new(controller);

Additionally, there won’t be gaps with the -> field accessor, it will be like:

float throttle = Controller->Axis3.value();

Pointers can be very powerful, but they can also be verrrrrry dangerous. The basic usage like you’re doing usually isn’t too bad. Linked lists, double pointers, things like that will make your head hurt or you’ll just get it intuitively. I’ve never met someone who is in between on pointers. :joy:

As a reference, here’s something I played with in making my own PID library, and wanting to abstract out motor vs motor group in the code to make things more flexible. (And yes, I could do things differently and make classes in C++, etc. etc., yes, I know. I’m a C guy through and through, sue me.)

In my .h file, I have something like this:

  vex::motor          *lMotorPtr;       //!*< Pointer to single left motor object
  vex::motor          *rMotorPtr;       //!*< Pointer to single right motor object
  vex::motor_group    *lMotorGroupPtr;  //!*< Pointer to left motor _group_ object
  vex::motor_group    *rMotorGroupPtr;  //!*< Pointer to right motor _group_ object

In my .c file, before main():

vex::motor      leftMotor(PORT1, true);
vex::motor      rightMotor(PORT6, false);

inside main(), I have:

  robotSetup.lMotorPtr = &leftMotor;
  robotSetup.rMotorPtr = &rightMotor;

And in the PID code, this snip:

  // Spin the motors to the desired position to true it up to its target distance
  lMotorPtr->spinTo(distanceTgtDeg, vex::rotationUnits::deg, false);   // Don't wait for complete
  rMotorPtr->spinTo(distanceTgtDeg, vex::rotationUnits::deg, true);

Hopefully that’s enough to give you a similar-to that will get you on the path to understanding!

2 Likes

and new comes with its own set of issues…

func fact, pointers are used extensively in the motorgroup class (partially as we have always avoided using any C++ standard library functionality, std::vector etc, in the vex classes to minimize any bloat they may cause).

void
motor_group::resetPosition() {
    for ( auto m: *_motors.pimpl ) {
      m->resetPosition();
    }      
}

I know. I’m a C guy through

me too, I learned C over 40 years ago, it’s in my DNA.

5 Likes

So, one thing that I did want to ask about, why not have motor_group just extend motor? I found out the “fun” way you can’t use a motor pointer for a motor_group and do things like set velocity. I would think motor_group would have just been a linked list of pointers to other motors. It certainly would have been a handy thing for code to just be able to point to the base type for either set up.

Turns out it’s a great way to hard-lock an IQ2 brain to the point you have to pull the battery to reset it. :sweat_smile:

1 Like

motor_group as subclass of motor, not sure that would have worked, accessing the first motor using a pointer to a motor_group is probably just some side effect of the way memory is organized in the motor_group class. If I had subclassed motor, well, I would have to think about that, some things (like the smartport index) are not relevant to motor_group, yea, it never occurred to think about it that way and overload all the functions of a motor.

The next (as yet unreleased) version of the sdk has a few protected member functions to give access to the underlying motors.

    protected:
      vex::motor *    operator[](int32_t index);
      vex::motor **   begin(); 
      vex::motor **   end();       

allows access to motors as an array and also provide iterator functions.
example,

vex::brain       Brain;
vex::motor       m1(PORT1);
vex::motor       m2(PORT2);

namespace vex {
  class motor_group2 : public motor_group {
    public: 
      using motor_group::motor_group;
      using motor_group::operator[];
  };
}

motor_group2  mg(m1, m2);

int main() {
    mg[0]->spin(forward, 100, percent);
    mg[1]->spin(forward, 100, percent);
}

yea, no easy way to prptect against that on IQ2.

2 Likes

what set of issues would new come with?

Well, memory leaks.

I’ve seen so much code where something was allocated using new and never deleted, PITA to find sometimes. or there’s no memory available, then what to do.

and now C++ has smart pointers, handles all that.

1 Like

First off, for learning C++, I always highly recommend this online C++ Compiler. Its basically a sandbox that lets you test snippets of code and see how they would work without having to have your robot nearby, setup a new project, etc.

Moving on, it has been said already that new can be very dangerous and have it’s own set of issues. This is a really deep rabbithole, but I think it’s important to describe why this can be so dangerous.

Whenever you write a program and make new variables, those variables require space in memory where they can be stored. From a beginner perspective, this is typically handled automagically and you don’t need to worry about it.

Many times in more complicated programs, we need to control this ourselves, requiring Dynamic Memory Allocation. This is a complicated subject, so the bare minimum TL;DR for operators like new is that if you borrow it, you have to give it back.

This following example will probably run perfectly fine for awhile, and then eventually explode and crash.

usercontrol(){

while(true){
  // Make a new controller
  Controller* controller = new Controller();
  
  // Get the value
  float throttle = controller->Axis3.value();
  
  // Do something with it
  ...
}

}

This happens because every loop, we borrow some memory to make a new controller, but we don’t return it. Eventually, we will run out of memory and the program will fail. This is called a Memory Leak.

In the old days, we would probably tell you that every new operator needs a corresponding delete operator.

If you aren’t working close to the embedded level or on something really really really specific, I would wager that you should never use the new operator at all (This could be a controversial take in some circles ¯_(ツ)_/¯). Rather, modern C++ recommends using a Smart Pointer instead.

Smart Pointers will automatically handle deletion of objects behind the scenes, even for things you have to Dynamically Allocate.

So, to sum that all up in a few lines (and why i wrote this post at all),

Do this:

std::shared_ptr<Controller> controller_ptr = std::make_shared<Controller>();
controller_ptr->Axis3.value();
...

Not this:

Controller* controller_ptr = new Controller();
controller_ptr->Axis3.value();
...
2 Likes

Thank you. I’ve definitely heard of memory allocation errors with C++ and the new operator, but I was very confused.

With that said, I have a couple issues.

this:

std::shared_ptr<controller> Controller = std::make_shared<controller>;

gives me an error:
No viable conversion from ' <overloaded function type>' to 'std::shared_ptr<controller>'

looking into other smart pointer declarations, I saw a post from stackoverflow which basically said that declaring smart pointers with new (such as this):

std::shared_ptr<controller> Controller(new controller(primary));

would work the same, assuming because the smart pointer can handle memory deallocation even with the new operator. Is this correct or incorrect?

The issue is that “make_shared” is a function, and you are missing the open and close parenthesis at the end. So instead of calling the function (which returns a shared ptr), you are trying to set the shared_ptr equal to the function.

If you add “()” to the end of your broken line, it should compile fine.

That said, the Stack Overflow post is also fine. I think technically it’s preferred to use make_shared over shared_ptr(new Thing()), but it will still work as intended (i.e. no memory leaks).

If you need to pass in parameters to make the object, you can add those as arguments to make_shared.

1 Like

Please, forgive me for taking the liberty to translate the highlights of this fascinating discussion into a form easily relatable to an average vf reader (strictly for the educational purposes). ty :pray:

But sometimes you gotta do it…

And, finally…

7 Likes

@Illyana you are my hero. Never would have thought to teach pointers through Spider Man memes, but now I know how to teach it to 4th and 5th graders!

4 Likes

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.