Search code examples
c++iteratorinstancesmember-functionsentity-component-system

Storing a vector of all class instances, and calling their member functions


How do I create a vector which stores all the instances of a class? Then how do I iterate over them and call one of their member functions?

Here's a condensed example of what I've been trying to do.

#include <vector>

struct Entity{

    Entity::Draw(){
        // do drawing things
    }
};

static std::vector<Entity> entities;

Entity player;
Entity enemy;

void renderEntities() {

    for (std::vector<Entity>::iterator iter = entities.begin();
            iter < entities.end();
            iter++) {

        iter->Draw; // Error in the example. I'm using Draw(); in the actual code.
}

But renderEntities() isn't doing anything. The Draw member function works, if I use e.g. player->Draw. I'm either screwing up the vector or the iterator or both and I can't figure out how to fix it. I've tried using references and pointers, which I assume is the thing to do, but whenever I try that I get errors that I haven't been able to fix.

UPDATE: I appreciate all the help, I'm learning a lot. However my render_entities function still isn't doing anything. Here's all the code.

Any function call that starts with terminal_ is from the BearLibTerminal library.

main.cpp

#include <BLT/BearLibTerminal.h>
#include <iostream>
#include <string.h>
#include <vector>

#include "entity.h"

const int WindowSizeX{50};
const int WindowSizeY{20};
const std::string Title{"BLT Test"};
const std::string Font{"../res/SourceCodePro-Regular.ttf"};
const int FontSize{24};

bool quit_game{false};

static Entity player;
static Entity enemy;

void initialize();
void handle_input(int key, Entity &entity);
void draw_player(int x, int y, const char *symbol);
void render_entities();
void clear_entities();

int main() {

    initialize();

    while (!quit_game) {

        terminal_refresh();

        int key{terminal_read()};

        if (key != TK_CLOSE) {

            handle_input(key, player);
        }

        else {

            quit_game = true;
            break;
        }

        clear_entities();
    }

    terminal_close();

    return 0;
}

void initialize() {

    terminal_open();

    std::string size{"size=" + std::to_string(WindowSizeX) + "x" +
                     std::to_string(WindowSizeY)};
    std::string title{"title='" + Title + "'"};
    std::string window{"window: " + size + "," + title};

    std::string fontSize{"size=" + std::to_string(FontSize)};
    std::string font{"font: " + Font + ", " + fontSize};

    std::string concatWndFnt{window + "; " + font};
    const char *setWndFnt{concatWndFnt.c_str()};

    terminal_set(setWndFnt);
    terminal_clear();

    player.x = 0;
    player.y = 0;
    player.layer = 0;
    player.symbol = "P";
    player.color = "green";

    enemy.x = 10;
    enemy.y = 10;
    enemy.layer = 0;
    enemy.symbol = "N";
    enemy.color = "red";
}

void handle_input(int key, Entity &entity) {

    int dx{0};
    int dy{0};

    switch (key) {

        case TK_LEFT:
        case TK_H:
            dx = -1;
            dy = 0;
            break;

        case TK_RIGHT:
        case TK_L:
            dx = 1;
            dy = 0;
            break;

        case TK_UP:
        case TK_K:
            dx = 0;
            dy = -1;
            break;

        case TK_DOWN:
        case TK_J:
            dy = 1;
            dx = 0;
            break;

        case TK_Y:
            dx = -1;
            dy = -1;
            break;

        case TK_U:
            dx = 1;
            dy = -1;
            break;

        case TK_B:
            dx = -1;
            dy = 1;
            break;

        case TK_N:
            dx = 1;
            dy = 1;
            break;

        case TK_ESCAPE:
            quit_game = true;
            break;
    }

    player.Move(dx, dy);

    if (player.x > WindowSizeX - 1) {

        player.x = WindowSizeX - 1;
    }
    else if (player.x < 0) {

        player.x = 0;
    }

    if (player.y > WindowSizeY - 1) {

        player.y = WindowSizeY - 1;
    }
    else if (player.y < 0) {

        player.y = 0;
    }

    player.Draw(); // This works.
    enemy.Draw();  // So do this.
    entity.Draw(); // This draws only player.
    render_entities(); // This doesn't do anything.

    // Player X and Y are printed out correctly, Entities is always 0.
    std::cout << "Player X: " << player.x << std::endl;
    std::cout << "Player Y: " << player.y << std::endl;
    std::cout << "Entities: " << entities.size() << std::endl;
}

void render_entities() {

    for (auto entity : entities) {
        entity->Draw();
    }
}

void clear_entities() {

    for (auto entity : entities) {
        entity->Clear();
    }
}

entity.h

#ifndef ENTITY_H_
#define ENTITY_H_

struct Entity {

    int x;
    int y;
    int layer;
    const char *symbol;
    const char *color;

    Entity();
    ~Entity();

    void Move(int dx, int dy);
    void Draw();
    void Clear();
};

static std::vector<Entity *> entities;

#endif /* ENTITY_H_ */

entity.cpp

#include <BLT/BearLibTerminal.h>
#include <vector>
#include <algorithm>
#include "entity.h"

Entity::Entity() {

    entities.push_back(this);
}

// Entity(const Entity &) : Entity() {}
// I get an "expected unqualified-id" when I uncomment this. Why?

Entity::~Entity() {

    auto iter = std::find(entities.begin(), entities.end(), this);

    if (iter != entities.end())
        entities.erase(iter);
}

void Entity::Move(int dx, int dy) {

    this->x += dx;
    this->y += dy;
}

void Entity::Draw() {

    terminal_layer(this->layer);
    terminal_color(color_from_name(this->color));
    terminal_print(this->x, this->y, this->symbol);
}

void Entity::Clear() {

    terminal_layer(this->layer);
    terminal_print(this->x, this->y, " ");
}

In main.cpp, at the bottom of handle_input() you will see...

    player.Draw(); // This works.
    enemy.Draw();  // So do this.
    entity.Draw(); // This draws only player.
    render_entities(); // This doesn't do anything.

    // Player X and Y are printed out correctly, Entities is always 0.
    std::cout << "Player X: " << player.x << std::endl;
    std::cout << "Player Y: " << player.y << std::endl;
    std::cout << "Entities: " << entities.size() << std::endl;

Solution

  • renderEntities() does not do anything because you didn't add any Entity objects to the vector. When you declare your player and enemy objects, they are just hanging around in memory, they are not automatically added to the vector. You need to add them explicitly, such as by calling entities.push_back().

    I would suggest using the Entity constructor and destructor to update the vector automatically, instead of you having to remember to do it manually. That way, every Entity object is accounted for by renderEntities(), eg:

    #include <vector>
    #include <algorithm>
    
    struct Entity;
    static std::vector<Entity*> entities;
    
    struct Entity
    {    
        Entity()
        {
            entities.push_back(this);
        }
    
        Entity(const Entity &)
            : Entity()
        {
        }
    
        ~Entity()
        {
            auto iter = std::find(entities.begin(), entities.end(), this);
            if (iter != entities.end())
                entities.erase(iter);
        }
    
        void Draw()
        {
            // do drawing things
        }
    };
    
    Entity player;
    Entity enemy;
    
    void renderEntities()
    {
        for (auto *entity : entities)
        {
            entity->Draw();
        }
    }
    

    Live Demo


    UPDATE: after seeing your full code, I can see that you are still making some mistakes.

    In main.cpp, there is no entity variable in scope of handle_input(), so calling entity.Draw() should not compile.

    The really big mistake is in entity.h, though. DO NOT declare your entities variable as static in that file! This causes every .cpp which #include's your entity.h file to get its own copy of the variable. This means that main.cpp and entity.cpp are operating on separate std::vector objects! That is why you see entities is always empty in main.cpp - the Entity objects are never being added to the std::vector that exists in main.cpp, only to the std::vector that exists in entities.cpp.

    You need to move the actual std::vector variable into entity.cpp (without static), and then declare the variable as extern in entity.h so that all of your .cpp files can access and share that single variable.

    Try this instead:

    entity.h

    #ifndef ENTITY_H_
    #define ENTITY_H_
    
    #include <vector>
    #include <string>
    
    struct Entity {
        int x = 0;
        int y = 0;
        int layer = 0;
        std::string symbol;
        std::string color;
    
        Entity();
        Entity(const Entity&);
        ~Entity();
    
        Entity& operator=(const Entity&) = default;
    
        ...
    };
    
    extern std::vector<Entity *> entities; // <-- extern, NOT static!
    
    #endif /* ENTITY_H_ */
    

    entity.cpp

    #include <BLT/BearLibTerminal.h>
    #include <vector>
    #include <algorithm>
    #include "entity.h"
    
    std::vector<Entity *> entities; // <-- real variable, also NOT static!
    
    Entity::Entity() {
        entities.push_back(this);
    }
    
    Entity::Entity(const Entity &src) : Entity() {
        *this = src;
    }
    
    Entity::~Entity() {
        auto iter = std::find(entities.begin(), entities.end(), this);
        if (iter != entities.end())
            entities.erase(iter);
    }
    
    ...
    
    void Entity::Draw() {
        terminal_layer(layer);
        terminal_color(color_from_name(color.c_str()));
        terminal_print(x, y, symbol.c_str());
    }
    
    ...