【问题标题】:Storing a vector of all class instances, and calling their member functions存储所有类实例的向量,并调用它们的成员函数
【发布时间】:2018-11-16 18:53:56
【问题描述】:

如何创建一个向量来存储一个类的所有实例?那么我如何遍历它们并调用它们的成员函数之一呢?

这是我一直在尝试做的一个精简示例。

#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.
}

但是 renderEntities() 没有做任何事情。如果我使用 Draw 成员函数,例如播放器->绘制。我要么搞砸了向量,要么搞砸了迭代器,要么搞砸了两者,我不知道如何修复它。我尝试过使用引用和指针,我认为这是要做的事情,但是每当我尝试使用时,我都会遇到无法修复的错误。

更新: 我感谢所有的帮助,我学到了很多东西。但是我的 render_entities 函数仍然没有做任何事情。这是所有代码。

任何以 terminal_ 开头的函数调用都来自 BearLibTerminal 库。

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();
    }
}

实体.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_ */

实体.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, " ");
}

在 main.cpp 中,在 handle_input() 的底部你会看到...

    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;

【问题讨论】:

  • 1.您在哪里做实体-> push_back 或模拟? 2.你试过iter->Draw()吗?
  • iter &lt; entities.end() 应该是iter != entities.end()
  • iter-&gt;Draw; 应该是iter-&gt;Draw();

标签: c++ iterator instances member-functions entity-component-system


【解决方案1】:

自 c++ 11 以来使用 for 循环的更简单方法:

for( auto & entity : entities) {
     entity.Draw();
}

【讨论】:

  • 请改用auto &amp;entity,否则每次迭代都会复制一份
  • 或者,更好的const auto &amp; entity如果Draw成员函数是const,这是可能的。
【解决方案2】:

您想要iter != entities.end(); 而不是&lt;。此外,在此代码示例中,您忘记了 Draw 之后的括号。

【讨论】:

    【解决方案3】:

    renderEntities() 不执行任何操作,因为您没有将任何 Entity 对象添加到 vector。当您声明 playerenemy 对象时,它们只是在内存中徘徊,它们不会自动添加到 vector。您需要显式添加它们,例如通过调用entities.push_back()

    我建议使用Entity 构造函数和析构函数自动更新vector,而不必记住手动进行。这样,每个Entity 对象都由renderEntities() 占,例如:

    #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


    更新:看到你的完整代码后,我可以看到你仍然在犯一些错误。

    main.cpp中,handle_input()范围内没有entity变量,所以调用entity.Draw()不应该编译。

    不过,真正的大错误在于entity.h。不要在该文件中将您的entities 变量声明为static!这会导致每个 .cpp #includeentity.h 文件获得它自己的变量副本。这意味着main.cppentity.cpp单独的std::vector 对象上运行!这就是为什么您在main.cpp 中看到entities 始终为空的原因——Entity 对象永远不会被添加到存在于main.cpp 中的std::vector 中,只会添加到存在于entities.cpp 中的std::vector 中。

    您需要将实际的std::vector 变量移动到entity.cpp(不带static),然后在entity.h 中将变量声明为extern,以便您所有的.cpp 文件都可以访问和共享单个变量

    试试这个:

    实体.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_ */
    

    实体.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());
    }
    
    ...
    

    【讨论】:

    • 原始问题包含一个值向量,而不是指针。答案应符合要求的规范。
    • 我很感激他用指针展示了它。我在我原来的帖子中确实提到过我尝试过使用指针,但是,我是新手,遇到了一堆我无法弄清楚如何修复的错误。我喜欢使用构造函数和析构函数来更新向量的想法,但是我收到一个错误,告诉我 .find 不是成员。我试过包括 但没有骰子。
    • 为什么是 entities static ?为什么 playerenemy 不是?另外,entities 不应该是Entity 的静态成员吗​​?
    • @Tibblediggins 我的错,我一直忘记 std::vector 没有 find() 方法。我将其修复为使用std::find()
    • @SidS 当全局变量为static 时,其他翻译单元无法通过extern 访问它。理想情况下,Entity 应该在 .h 文件中声明。我想过让entities 成为Entity 的静态成员,但认为最好将其保密。不过,playerenemy 可以跨单元访问可能是有意义的
    【解决方案4】:

    有几种方法可以做到这一点,我会从最差到最好对它们进行排名(并不是说你的方法不好,只是在这种情况下有更好的方法)。

    你的代码的问题是iter-&gt;Draw; --> 这实际上并不是在调用函数,所以应该是:

    for (std::vector<Entity>::iterator iter = entities.begin();
            iter < entities.end();
            iter++) {
        iter->Draw(); // Notice I added ()
    }
    

    但是,有更好的方法来做同样的事情:

    // Entity & is important so that a copy isn't made
    for (Entity & entity : entities) {
        entity.Draw();
    }
    

    现在是上述的等效(但稍微好一点)版本:

    // Notice the use of auto!
    for (auto & entity : entities) {
        entity.Draw();
    }
    

    最后,如果您后来决定需要将它作为指针向量,您可以:

    static std::vector<Entity *> entities;
    
    static Entity * player = new Entity();
    static Entity * enemy = new Entity();
    
    ...
    
    for (Entity * entity : entities) { // You could also use (auto entity : entities)
        entity->Draw();
    }
    

    在这种情况下,由于它是raw pointers 的向量而不是smart pointers,因此您需要确保在调用entities.clear() 之前循环遍历向量并删除实体,否则您将有内存泄漏。

    关于最后一个示例的好处是,如果您稍后重新组织代码以拥有其他类 extend Entity 以提供它们自己的 Draw 行为,您的向量仍然能够存储指向所有这些新的指针类并调用它们的 Draw() 方法。

    【讨论】:

    • 谢谢,很有帮助。不过,我仍然遇到 render_entities 的问题。我已经用完整的源代码更新了我的帖子。
    • @Stephano 为什么选择使用原始指针向量而不是智能指针向量?
    • @RemyLebeau 在实际代码中你不会,但我不想在我的帖子中解释智能指针,所以我把它们排除在外
    • @Tibblediggins 很高兴你得到了答案!
    【解决方案5】:

    这是一个例子。

    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    
    struct Entity {
        Entity(string _name):name(_name){} 
        void Draw(){
            // do drawing things
            cout << name << "::Draw" << endl;
        }
        private:
        string name;
    };
    
    static std::vector<Entity *> entities;
    
    static Entity * player = new Entity("Player");
    static Entity * enemy = new Entity("Enemy");
    
    void renderEntities() 
    {
        for( auto & entity : entities){
           entity->Draw();
        }
    }
    
    int main()
    {
       entities.push_back(player);
       entities.push_back(enemy);
    
       renderEntities();
    
       return 0;
    }
    

    【讨论】:

    • 您正在泄漏Entity 对象。考虑改用std::vector&lt;std::unique_ptr&lt;Entity&gt;&gt;
    猜你喜欢
    • 2013-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-27
    相关资源
    最近更新 更多