Search code examples
c++gccg++sfmltic-tac-toe

C++ "Duplicate definition error", but not sure where


I'm trying to make TicTacToe with SFML as a beginner project, to figure stuff out first I tried to display 9 boxes on my window (grid like), but I'm getting a duplicate definition error and I'm not really sure where the problem is to be honest. Would appreciate it if you gave it a look.

main.cpp:

#include "grid.h"

using sf::Event, sf::Keyboard;


int main() {
    // game objects
    sf::RenderWindow window(sf::VideoMode(500, 500), "TicTacToe", sf::Style::Titlebar || sf::Style::Close);
    sf::Event EVENT;
    
    grid::Init();

    // main loop
    while (window.isOpen()) {
        
        // events handler
        while (window.pollEvent(EVENT)) {
            switch (EVENT.type) {
                // window closed
                case Event::Closed:
                    window.close();
                    break;

                // ESCAPE key pressed
                case Event::KeyPressed:
                    if (Keyboard::isKeyPressed(Keyboard::Escape)) {
                        window.close();
                    }

                    break;
            }
        }

        // renderer
        window.clear();

        // game grid
        grid::Render(window);

        window.display();
    }

    return 0;
}

grid.h:

#ifndef TILE_H
#define TILE_H

#include <vector>
#include <iostream>

#include <SFML/Graphics.hpp>

using sf::RenderTarget, std::vector, sf::RectangleShape, sf::Color, sf::Vector2f;


namespace grid {
    // classes
    class Cell {
        private:
            // attributes
            RectangleShape shape;

        public:
            // constructor - destructor
            Cell(int size, float xPos, float yPos, Color color);
            virtual ~Cell();

            // accessors
            RectangleShape getShape();

            // modifiers
            void setSize(int size);
            void setPosition(float xPos, float yPos);
            void setColor(Color color);
    };

    // variables
    vector<Cell> cells;

    // functions
    void Init();
    void Render(sf::RenderTarget& target);
};


#endif // #ifndef TILE_H

grid.cpp:

#include "grid.h"


// functions
void grid::Init() {
    std::cout << "initalizing grid" << std::endl;

    int cellSize = (500 / 3) - (10 * 2);
    float xPos = 0;
    float yPos = 0;

    for (int i = 0; i < 9; i++) {
        xPos += (500 / 3);

        if (i % 3 == 0) {
            yPos += (500 / 3);
        }

        grid::Cell newCell(cellSize, xPos, yPos, Color::White);
        grid::cells.push_back(newCell);
    }

    std::cout << "initalized grid" << std::endl;
}

void grid::Render(sf::RenderTarget& target) {
    for (auto cell: grid::cells) {
        target.draw(cell.getShape());
    }
}


// constrictor - destructor
grid::Cell::Cell(int size, float xPos, float yPos, Color color) {
    grid::Cell::setSize(size);
    grid::Cell::setPosition(xPos, yPos);
    grid::Cell::setColor(color);
}

grid::Cell::~Cell() {}


// accessors
RectangleShape grid::Cell::getShape() {
    return grid::Cell::shape;
}


// modifiers
void grid::Cell::setSize(int size) {
    grid::Cell::shape.setSize(Vector2f(size, size));
}

void grid::Cell::setPosition(float xPos, float yPos) {
    grid::Cell::shape.setPosition(Vector2f(xPos, yPos));
}

void grid::Cell::setColor(Color color) {
    grid::Cell::shape.setFillColor(color);
}

makefile:

main: grid.cpp main.cpp
    g++ grid.cpp main.cpp -o "tictactoe" -I C:/Users/Marsel/Documents/SFML-2.5.1/include -L C:/Users/Marsel/Documents/SFML-2.5.1/lib -D SFML_STATIC -lsfml-graphics -lsfml-window -lsfml-system && tictactoe.exe

Output (Error):

g++ grid.cpp main.cpp -o "tictactoe" -I C:/Users/Marsel/Documents/SFML-2.5.1/include -L C:/Users/Marsel/Documents/SFML-2.5.1/lib -D SFML_STATIC -lsfml-graphics -lsfml-window -lsfml-system && tictactoe.exe
c:/programdata/chocolatey/lib/mingw/tools/install/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/11.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\Marsel\AppData\Local\Temp\ccqymyhM.o:main.cpp:(.bss+0x0): multiple definition of `grid::cells'; C:\Users\Marsel\AppData\Local\Temp\cca8rixi.o:grid.cpp:(.bss+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
make: *** [main] Error 1
[Finished in 9.1s]

Solution

  • To understand what's going on here, we first have to understand how header files and #include work in C++.

    In C++, if you have the files:

    example.h

    class Example {
        // ...
    };
    

    example.cpp

    #include "example.h"
    
    Example obj; 
    

    Then, the C++ compiler will just copy-paste example.h into example.cpp, like so:

    example.cpp after including example.h

    class Example {
        // ...
    };
    
    Example obj; 
    

    Basically, #include "example.h" just means "Take the content of file example.h, and put that into this file"

    This can become a problem sometimes, however. Let's take a look at a simplified version of your code (I've removed everything that isn't relevant):

    grid.h

    #include <vector>
    
    namespace grid {
        class Cell {
            // ...
        };
    
        std::vector<Cell> cells;
    }
    

    grid.cpp

    #include "grid.h"
    
    // define functions ...
    

    main.cpp

    #include "grid.h"
    
    int main() {
        // ...
    }
    

    After the #include "grid.h" is replaced with the file grid.h, this is what the resulting .cpp files look like*:

    grid.cpp

    #include <vector>
    
    namespace grid {
        class Cell {
            // ...
        };
    
        std::vector<Cell> cells;
    }
    
    // define functions ...
    

    main.cpp

    #include <vector>
    
    namespace grid {
        class Cell {
            // ...
        };
    
        std::vector<Cell> cells;
    }
    
    int main() {
        // ...
    }
    

    Can you see the problem? When the compiler compiles grid.cpp, it finds the variable grid::cells, but when it compiles main.cpp, it finds another variable that's also called grid::cells! Which one should it use? It can't figure that out, and so it returns the error "multiple definition of grid::cells".

    There are 2 ways to fix this.

    1. extern. Define cells as extern std::vector<Cell> cells; in grid.h. This tells the compiler "There is a variable grid::cells, but it's not defined here, please look for it somewhere else." And then in grid.cpp, simply add the line std::vector<grid::Cell> grid::cells; which tells the compiler "You know that variable grid::cells I told you existed? Well, it's defined here, so please use this variable whenever you see grid::cells"

    2. inline. Define cells as inline std::vector<Cell> cells; in grid.h. This tells the compiler "I know that this variable (grid::cells) will be in multiple .cpp files, but I only want there to be one variable grid::cells, not two, so could you please make sure that all grid::cells variables are the same?"

    In your case, both of these should work. inline might be easier (you don't need to add anything to grid.cpp).**


    • Note that #include <vector> would also be replaced with the file containing the implementation of the std::vector class. But let's ignore that since it's not relevant in this case; we only care about the grid.h file.

    ** Note that using inline like this is only possible since C++17, so if you need to use very old versions of C++ (or if you're writing C) you need to use the extern option.