【问题标题】:Checking if object exists when returning from a function with for loop从带有for循环的函数返回时检查对象是否存在
【发布时间】:2016-01-04 22:45:22
【问题描述】:

我正在寻找安全使用此类代码的正确方法:

renderer->renderEntity(entityManager->getEntity("player"));

Entity EntityManager::getEntity(string entityName) 
{
    for (int index = 0; index < entityVector.size(); ++index)
    {
        if (entityVector[index].getName() == entityName)
        {
            return entityVector[index];
        }
    }
}

您希望从类实例中的容器中获取对象,并希望检查它是否存在,因此您不会调用不存在的对象。

我知道我可以将调用更改为:

if (entityManager->getEntity("player" != nullptr)
{
    renderer->renderEntity("player");
}

我想避免双重调用来检查对象是否存在。我想这可能更像是一个设计而不是语法问题。我可以将错误检查构建到包含 for 循环的 getEntity() 函数中吗?我不确定,因为返回值是实体,所以必须返回一个实体对象。 (如果是指针也一样,这不是我项目中的代码,只是一个类似的例子)。

【问题讨论】:

  • 同名实体不存在的情况有多普遍?如果这种情况很少发生,您可以抛出异常。另一种选择是采用标准库方法并将迭代器返回到元素,如果不存在则返回到 entityVector.end()
  • 如果实体不存在,你的方法应该返回什么?
  • 我建议使用std::map
  • 您可以创建一个默认对象,如果该项目不存在则返回该对象;该对象可能有一些良性行为,或者它可能有一个方法告诉你它不是真实的,或者它可能为每个方法抛出一个异常。
  • 您可以将 renderEntity 更改为 renderEntityIfExists 并在传入错误实体时返回

标签: c++ oop for-loop error-handling


【解决方案1】:

如果可以更改返回类型:让 getEntity() 返回一个指针,如果对象不存在则返回 NULL。

或者,正如@TartanLlama 建议的那样,您可以返回一个迭代器。

其他选项(仅在某些情况下有效):您的方法可以记住上次调用它的entityName 和结果索引。输入时首先检查当前输入是否等于前一个输入:在这种情况下,您无需再次搜索就返回存储的索引。

【讨论】:

  • 这可行,但我希望在使用对象之前不必检查对象是否为 NULL,因此我没有很多 if 语句阻塞代码。你的第二点很好。我喜欢这样,如果您正在寻找最近发现的对象,请跳过搜索。绝对是我可以用来提高效率的东西,谢谢!
  • 检查对象是否为NULL与检查元素是否存在相同,它们是相同的情况。我看不出它有什么帮助,除非这个方法 findEntityByName() 如果元素不存在则生成它。
【解决方案2】:

这应该会有所帮助。一个简单的空对象模式

https://en.wikipedia.org/wiki/Null_Object_pattern

class Entity
{
public:
    virtual void render()
    {
        // do somthing
    }
};

class EmptyEntity final : public Entity 
{
public:
    void render() override
    {
        //Do nothing or print log
    }
} static g_EmptyEntity;

Entity EntityManager::getEntity(string entityName) 
{
    for (int index = 0; index < entityVector.size(); ++index)
    {
        if (entityVector[index].getName() == entityName)
        {
            return entityVector[index];
        }
    }
    return g_EmptyEntity;
}

【讨论】:

  • 这几乎正是我想要的,我没有想到覆盖任何功能。非常好的解决方案,我认为会有一个设计模式!谢谢。
  • 当你按值返回时,你得到了对象切片。
  • 是的 Jarod42。但由于提问者没有更多细节,所以我不能包括所有细节。 Entity& EntityManager::getEntity(string entityName) 可以帮忙
  • 引用返回是正确的,否则EmptyEntity::render()永远不会被EntityManager::getEntity("NotFound").render()调用。
【解决方案3】:

返回指针会更简单/更清晰:

Entity* EntityManager::findEntityByName(const std::string& entityName) 
{
    const auto it = std::find_if(entityVector.begin(), entityVector.end(),
                                 [&](const auto& e) {
                                      return e.getName() == entityName;
                                 } );
    if (it == entityVector.end()) { // Not found.
        return nullptr;
    }
    return std::addressof(*it);
}

然后

auto* entity = entityManager->findEntityByName("player");
if (entity  != nullptr)
{
    renderer->renderEntity(entity);
}

【讨论】:

  • 我希望得到的是在使用它进行调用之前我不需要检查对象是否为空的情况。但是你的答案中有一些非常好的东西,我不知道 std::find_if() 和 std::addressof()。谢谢,它们将来对我有用!