c++oopsfmllnk2001

C++ OOP LNK2001 ERROR


As the title suggests, I have an OOP error in C++, the LNK2001 unresolved externals error. Here is my code, where did I go wrong? I'm using sfml to use the graphics in VS2015.

// OOPExample.hpp
#pragma once
#include <SFML\Graphics.hpp>
#include <SFML\System.hpp>
#ifndef OOPEX_H
#define OOPEX_H

class OOPExample{
public:
    static sf::CircleShape shape;
    static float rot;
    static void draw();
    static void rotate();
};
#endif // OOPEX_H

// OOPExample.cpp

#include "OOPExample.hpp"
#include <SFML\Graphics.hpp>

void OOPExample::rotate() {
    OOPExample::rot += 0.1f;
    return;
};

void OOPExample::draw() {
    OOPExample::shape.setFillColor(sf::Color::Red);
    OOPExample::shape.setRotation(rot);
    return;
};

// Source.cpp

#include <SFML/Graphics.hpp>
#include "OOPExample.hpp"

int main()
{
    sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
    OOPExample oopexample();

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
        window.draw(oopexample().shape);
        window.display();
    }

    return 0;
}

From what I've seen it looks like I need a definition of the methods declared in OOPExample.hpp, but I have those exact definitions in OOPExample.cpp. Am I instantiating the class incorrectly in Source.cpp?


Solution

  • To your question about the link errors. The comments highlight most of them... These are not OOP errors but rather link time errors when you build your project. I don't know if you have any experience with compiled system-level languages; but it would be a good idea to learn the basics of the compile-link cycle and what is expected by the linker when putting the final program together. A simple example of how to define a static member variable follows.

    // class1.h
    
    Class1
    {
    public:
    private:
      static float rotation;
    };
    
    // class1.cpp
    
    #include "class1.h"
    
    int Class1::rotation = 5.0f;
    

    Note that int Class1::rotation = 5.0f; happens once at program initialisation.

    I don't know if you are following some tutorial where they are creating this class like this, but you have a worrying amount of static members. This is an OOP problem. If you want to make many objects/instances of OOPExample you need to understand what static means. In the context of a class, when you apply the static keyword to a variable it means that all OOPExample objects will share that one variable. This makes static member variables good for default values and things like the number of a given class. You could have a static int OOPExample::count; to count the number of OOPExample objects that you have made. I will put that in an example later.

    There may be many reasons for link errors, particularly missing definitions. PcAF highlighted an important one in the comments to your question. But you may also be missing the SFML libraries. I vaguely remember SFML tutorials including detailed instructions on how to link their libraries in your environment. In VS that will be somewhere in your project properties. You will obviously get similar errors if you declared something in the header that is not in the implementation (usually the cpp). This was the case with your static variable, but applies to functions as well.

    Now there is a lot wrong with the three files you provided. I edited them to highlight some of the problems but it is far from perfect. I would not approach it in this way because sf::CircleShape is already an object-orientated entity. It has all the things you are trying to implement already implemented. Never over-abstract a problem (I also realised at some point that we were rotating a filled circle haha). You should really follow the advice to get a good textbook and start from the ground-up. SFML is a huge library that will distract you from understanding the fundamentals of C++. OOP is but one aspect of C++ and you need to embrace all C++ fundamentals to use OOP effectively. If you do this, you will have the most powerful abstraction mechanisms available (in my opinion).

    My edits follow, but really, it is just a demonstration of how deep the rabbit hole goes (it gets much worse). How to instantiate OOPExample is shown in main.

    // OOPExample.h
    #ifndef OOPEX_H
    #define OOPEX_H
    
    // Only include what you need to. Users of this header should be exposed to as
    // little SFML as possible.
    #include <SFML/Graphics/CircleShape.hpp>
    
    class OOPExample{
    public:
        // Parameterless constructor.
        OOPExample(); // Note, this sets the rotation to the default rotation.
        // One that takes a initial rotation.
        OOPExample(float initial_rotation);
    
        // Rotate 0.1f (by default) or by user specified amount.
        void rotate(float rotation = 0.1f);
    
        // window.draw() takes a Drawable as its first argument. Fortunately,
        // CircleShape is a shape which in turn is a Drawable. Notice that we
        // return by constant reference. Callers cannot edit our shape but they
        // get a reference to the sf::CircleShape shape instance so they can read
        // it.
        // const, & (i.e. reference), pointers requires a deep understanding of
        // object ownership, copying by value, by reference, and now of
        // particular interest in C++11, moving.
        const sf::CircleShape &getShape() const;
    
        // You forgot to declare and define this.
        void setRotation(float rotation);
        // Set the default rotation for all objects created with the
        // parameterless constructor.
        static void setDefaultRotation(float rotation);
    
        // The destructor.
        virtual ~OOPExample();
    private:
        sf::CircleShape shape;
        // sf::CircleShape already has a rotation with getters and setters.
        // Don't over abstract!
        // Our setRotation, rotate functions seem a bit unneccesary.
        //   float rotation;
    
        // Defaults.
        static sf::CircleShape default_circle;
        static float default_rotation;
        // Count example.
        static int count;
    };
    
    #endif // OOPEX_H
    
    
    // OOPExample.cpp
    
    // Personally, and I know with most other C++ developers, I prefer my header's
    // extension to be .h. .hpp usually identifies headers with
    // implementations/definitions of classes in the header file itself (necessary
    // in some circumstances).
    #include "OOPExample.h"
    
    //
    // How to initialise static member variables. This happens once at the
    // beginning of the program.
    //
    // All our circles have a default radius of 5.
    sf::CircleShape OOPExample::default_circle = sf::CircleShape(5);
    // And rotation of 0.
    float OOPExample::default_rotation = 0;
    
    int OOPExample::count = 0;
    
    // The definition of the parameterless constructor.
    OOPExample::OOPExample()
    // A class initialiser list. How we build a new object.
        : shape(default_circle)     // We copy the default circle.
    {
        // Do other stuff to construct the object if you need to. For example:
        shape.setFillColor(sf::Color::Red);
        setRotation(default_rotation);
    
        count++; // We just made another OOPEXample instance.
    }
    
    // The definition of a constructor that takes an initial rotation. I just
    // realised we are rotating a circle!
    OOPExample::OOPExample(float initial_rotation)
        : shape(default_circle)     // We copy the default circle.
    {
        // Do other stuff to construct the object if you need to. For example:
        shape.setFillColor(sf::Color::Red);
        // Notice: we used the user provided argument this time.
        setRotation(initial_rotation);
    
        count++; // We just made another OOPEXample instance.
    }
    
    void OOPExample::rotate(float rotation)
    {
        shape.rotate(rotation);
    
        // return; // No need to specify a return for a void.
    }
    
    const sf::CircleShape &OOPExample::getShape() const
    {
        return shape;
    }
    
    void OOPExample::setRotation(float rotation)
    {
        shape.setRotation(rotation);
    }
    
    void OOPExample::setDefaultRotation(float rotation)
    {
        // OOPExample scoping is unnecessary.
        OOPExample::default_rotation = rotation;
    }
    
    OOPExample::~OOPExample()
    {
        // Do things required for cleanup, i.e. deinit.
    
        // One OOPExample just reached the end of its lifetime. Either it
        // was deleted or reached the end of the
        // scope (i.e. {}) it was created in.
        count--;
    }
    
    
    // main.cpp
    
    #include "OOPExample.h"
    
    #include <SFML/Graphics.hpp>
    
    int main()
    {
        sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
    
        // Call the default, parameterless, constructor to instantiate an object
        // of OOPExample.
        OOPExample oopexample;
        // Create another with a initial rotation of your choosing.
        OOPExample another_obj(0.5f);
    
        while (window.isOpen())
        {
            sf::Event event;
            while (window.pollEvent(event))
            {
                if (event.type == sf::Event::Closed)
                    window.close();
            }
    
            window.clear();
            // The instance object of OOPexample is referred to by oopexample.
            // window.draw(oopexample().shape);
            // This member is now private.
            //window.draw(oopexample.shape);
            window.draw(oopexample.getShape());
            window.display();
        }
    
        return 0;
    }