Unidentified reference to class member function error :(

I get the error:

/var/folders/9k/5q2b1vds4nbbdyvr7t_3zlrh0000gn/T//ccOOqDaa.ltrans0.ltrans.o: In function `playerInput':
/Users/sussymcdonalds/Downloads/ARDUBOY/Untitled-Platformer/player.cpp:16: undefined reference to `player'
/Users/sussymcdonalds/Downloads/ARDUBOY/Untitled-Platformer/player.cpp:20: undefined reference to `player'
/Users/sussymcdonalds/Downloads/ARDUBOY/Untitled-Platformer/player.cpp:20: undefined reference to `player'
/Users/sussymcdonalds/Downloads/ARDUBOY/Untitled-Platformer/player.cpp:20: undefined reference to `player'
/Users/sussymcdonalds/Downloads/ARDUBOY/Untitled-Platformer/player.cpp:20: undefined reference to `player'
/var/folders/9k/5q2b1vds4nbbdyvr7t_3zlrh0000gn/T//ccOOqDaa.ltrans0.ltrans.o:/Users/sussymcdonalds/Downloads/ARDUBOY/Untitled-Platformer/player.cpp:20: more undefined references to `player' follow
collect2: error: ld returned 1 exit status
exit status 1
IntelliSense configuration already up to date. To manually rebuild your IntelliSense configuration run "Cmd+Alt+I"
[Error] Analyzing sketch 'Untitled-Platformer.ino': Exit with code=1

player.h:

#pragma once
#include <stdint.h>
#include <Arduboy2.h>

class Player
{
    private:

    struct Physics
    {
        static constexpr float gravity = 0;
        static constexpr float friction = 0;

        static constexpr float cutOffPoint = 0;
    };

    struct PlayerProperties
    {
        float x;
        float y;

        float xVelocity;
        float yVelocity;

        static constexpr uint8_t size = 8;

        static constexpr float speed = 0.5;

        bool isPlayerRight;
    };

    Physics physics;
    PlayerProperties playerProperties;

    void playerInput();

    void drawPlayer();

    public:

    void updatePlayer();

    Arduboy2 arduboy;
};

extern Player player;

player.cpp:

#include "player.h"
#include "sprites.h"

void Player::updatePlayer()
{
    playerInput();

    playerProperties.yVelocity += Physics::gravity;

    playerProperties.x += playerProperties.xVelocity;
    playerProperties.y += playerProperties.yVelocity;
}

void Player::playerInput()
{
    playerProperties.isPlayerRight = true;

    if (arduboy.pressed(RIGHT_BUTTON))
    {
        playerProperties.xVelocity += PlayerProperties::speed;
        playerProperties.isPlayerRight = true;
    }

    if (arduboy.pressed(LEFT_BUTTON))
    {
        playerProperties.xVelocity -= PlayerProperties::speed;
        playerProperties.isPlayerRight = false;
    }
}

void Player::drawPlayer()
{
    Sprites::drawOverwrite(playerProperties.x, playerProperties.y, playerSprite, playerProperties.isPlayerRight);
}

I’ve been shaking my head over this for hours at this point. Everything seems fine however.

Also uhh . . . anyone know a good light theme for VSCode? All the light themes appear waay too bright.

Congratulations, you’ve encountered what is probably your first linker error.

The short version: you’ve declared your player as a global variable via extern Player player;, but you need to define your player somewhere.

In your case the simplest option would be to put Player player; in your .cpp file.


I’m concerned about the fact you’ve put an Arduboy2 object in the player though.
You’re only supposed to have one Arduboy2 object.

Much as I dislike the practice, if you’re relying on global variables you’re probably best off having a single Globals.h and Globals.cpp, declaring all your global variables (as extern) in the .h file, and defining all your global variables in the .cpp file.

Also, you probably don’t need that PlayerProperties class, you might as well just put thos properties directly into the Player class. Or are you planning to somehow use PlayerProperties for something other than the Player class?

2 Likes

How can you define an object though? By using it?

But it’s in game.h

I just wanted everything to be organized, but I guess it doesn’t need a struct.

I haven’t got long so this’ll be a very quick answer…

Declaration:

extern Player player;
extern Arduboy2 arduboy;

Definition:

Player player;
Arduboy2 arduboy;

More or less everything (function, variable, class, et cetera) has both a ‘declaration’ and ‘definition’ form. It’s to do with the archaic way the compiler works (which I’ll explain in-depth some other time).

You’ve created another one inside your Player:

That’s not the same object as the one in your game.h, that’s an entirely different one, i.e. you now have (at least) two arduboy objects: arduboy (in the global namespace) and player.arduboy. (If you’d had player1 and player2 instead of just player then you’d also have player1.arduboy and player2.arduboy, and thus 3 arduboy objects.)

It’s still organised if you put it directly into Player.

Getting the balance right in terms of separation is something that takes practice and experience. You don’t want too little subdivision, but you don’t want too much either.

1 Like

Oh, I see. So the compiler just looks for one single definition in a .cpp, which is why defining it in two files gave me an error (not this post’s error), and not defining it gave me an error.

Yeah, I removed the arduboy object from the player class and just included game.h instead.

That’s true. You could have a struct in a class in a namespace. Or, you could have none of those.

1 Like

Exactly. In fact there’s even a name for this. It’s called the one definition rule, or “ODR” as it is often referred to. (See also: Wikipedia article.)

Again, there are reasons for this and it’s to do with the (rather archaic) way the compiler works.

Bottom line: you can declare things as many time as you like, but they must be defined no more than once.

Since header files are often included more than once in separate translation units (think .cpp/.ino files, I’ll explain this properly another time*) you generally don’t want to define a global in a header, only declare it.

(* If you’re feeling really brave, see phases of translation.)

The rules for other things are slightly different:

  • A class/struct and its member functions can be defined in a header without issue. (And in fact a class/struct must be properly defined before you can create an instance of it.)
  • Functions may be defined in a header file if they are marked inline or constexpr (because constexpr implies inline).

In C++17 they finally allowed inline to work on global variables too, thus making it possible to define everything in a header, but Arduino code is stuck in C++11 land, so you won’t be able to do that in an Arduboy game sadly.

That’s more along the lines of the sort of thing you’re supposed to do.

You could, and sometimes it would make sense to do so, but you need a good reason.

Everything you do in terms of design should have a reason behind it.

Again, technically you could, but then you’d pretty much be living in C-land and that would defeat the point of using C++.

One of my favourite Bjarne Stroustrup quotes is:

"C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off. "

2 Likes