Where to declare a new object with parameters?

I’m a C++ beginner so I apologize for the probably trivial question. I’ve tried searching around but I suspect I’m not using quite the right terms so I’m hoping someone here can correct my thinking.

As I’ve been learning OpenFrameworks and making quick little sketches, I’ve been lazy and just put all of my class definitions and global variables at the top of my “ofApp.cpp” file. However as an exercise in writing cleaner code and separating classes out into individual files I tried to move my “Ball” class into a separate file. The lazy way I did it before had the methods and constructor function code inside of the Class definition. So the top of my ofApp.cpp file looked like this.

class Ball {
public:

    ofVec2f pos;
    ofVec2f speed;

    void update(){
         pos = pos + speed;

         if (pos.x > ofGetWidth() || pos.x < 0) {
             speed.x *= -1;
         }
         if (pos.y > ofGetHeight() || pos.y < 0) {
             speed.y *= -1;
         }

         ofDrawCircle(pos.x, pos.y, 20);

    }
    
    Ball(float posx_, float posy_, float speedx_, float speedy_){

        pos.set(posx_, posy_);
        speed.set(speedx_, speedy_);
    }
};

Ball ball(10, 10, 2, 3);

Then

void ofApp::draw(){
ball.update();
}

This is how I’ve been doing things, and while it works I understand it’s not best practice. So I created a “Ball.cpp” and “Ball.h” file and attempted to copy the code over.

Inside of “Ball.h” I have:

#ifndef Ball_hpp
#define Ball_hpp
#include <ofMain.h>

class Ball {
public:

    ofVec2f pos;
    ofVec2f speed;
    
    void update();
    
    Ball(float posx_, float posy_, float speedx_, float speedy_){
        pos.set(posx_, posy_);
        speed.set(speedx_, speedy_);
    }
};

#endif /* Ball_hpp */

Inside of “Ball.cpp” I have:

#include "Ball.h"

void Ball::update(){
    pos = pos + speed;
    if (pos.x > ofGetWidth() || pos.x < 0) {
        speed.x *= -1;
    }
    if (pos.y > ofGetHeight() || pos.y < 0) {
        speed.y *= -1;
    }

    ofDrawCircle(pos.x, pos.y, 20);
}

My main problem is I’m not sure where to move this piece of code that was previously at the top of my “ofApp.cpp” file.

Ball ball(10, 10, 2, 3);

I have tried declaring that directly into the ofApp class in ofApp.h.

I’ve tried just declaring “Ball ball;” in ofApp.h, and then calling ball(10, 10, 2, 3); in the setup() loop of ofApp.cpp

I’ve tried both of those things in the Ball.h file, and also tried throwing “Ball ball(10, 10, 2, 3);” directly at the end of my Ball.cpp file. All of those things appear to be incorrect from the errors I get when I try to run.

If I move the constructor into a setup() method in the ball class, I can get everything running correctly calling ball.setup(10,10,2,3) in the ofApp::setup() function. I guess I can keep doing that
but I’m curious if anyone can point out where I’m going wrong with the constructor approach. Thanks in advance!

Hi @aarbrion ,

Constructors are not trivial at all, and there is lots to learn about them and they’re a very deep and important part of c++. I’ve always found them and related functions a bit complicated, so I try to keep things simple and use the default versions.

Compilers will use default constructors when needed, in addition or in place of any custom ones that are specified. There are also default versions for destructors, and functions related to copy, assignment, and move. Customizing one can often require customizing several; have a look at the “rule of 5” for more info.

Calling Ball ball in ofApp.h will create a new, empty ball with a default constructor. This ball can be further modified in ofApp::setup() (or anywhere) as needed. I like this approach, because it relies on a default constructor, and its simple, explicit, and easy to read, modify, and maintain:

// in ofApp.h (or in any local scope in ofApp.cpp)
// create an empty Ball with the default constructor
Ball ball;

// customize it in ofApp::setup() or anywhere in ofApp.cpp
ball.setPosition(10, 10);
// or
ball.setPosition(ofVec2f(10, 10));
// or even
ball.setPosition(glm::vec2(10, 10));

Now, you could also create another Ball in ofApp::setup() with the customized constructor, and assign it to the class member with something like this:

// in ofApp.h
Ball ball;

// in ofApp.setup()
// create another ball with a custom constructor and assign it with a default assignment
ball = Ball(10,10,2,3);

In this approach, the default assignment constructor (?) will likely (?) make a copy of the new Ball that was created from the custom constructor, and then assign that copy to the Ball in ofApp. Complicated I know, and it usually works OK unless pointers (or maybe dereferenced pointers) are involved. Compilers can optimize code in different ways, and some compilers might choose to move the custom-constructed object instead of making a copy.

And this might be worth a try too:

// in ofApp.h
Ball ball = Ball(10, 10, 2, 3);

You could also try specifying default values in the custom constructor, and I’d bet that the compiler would pick it over the default one when creating an “empty” Ball (ie no arguments supplied):

// in Ball.h
Ball(float posx_ = 10, float posy_ = 10, float speedx_ = 2, float speedy_ = 3)
{
        pos.set(posx_, posy_);
        speed.set(speedx_, speedy_);
}

// this could also be a custom constructor:
Ball()
{
    pos = Vec2f(10, 10);
    speed = Vec2f(2, 3);
}

There are some other syntactic variations if this that will get similar results, though I’ve not used them and can’t quite remember them just now.

Also, I love the separation between the .h and .cpp files. I think its more important when the classes get complex, with lots of members and/or functions. All of the oF classes are set up like this, more or less. So have a look at one or a few of them on the GitHub page if you’re interested to see how this separation can be helpful.

OK hope this helps and have fun experimenting!

2 Likes

Thanks so much for the detailed response @TimChi !

I appreciate the different approaches as it’s helping me wrap my head around this stuff. I don’t know why I felt like I needed to use a constructor vs a custom ball.setup() function for initial values. I can definitely see why the approach in your first example is more explicit and easy to read.

I think part of my problem was not having any default values for the constructor to use. I still don’t fully understand why Ball ball = Ball(10, 10, 2, 3) can still work in the header file and not Ball ball(10, 10, 2, 3)—when the latter works inside the ofApp.cpp file. I also discovered Ball ball{10, 10, 2, 3) with curly bracket notation works!

But anyway, I have several solutions to use in the future now, so thanks again!