2

I have a class called Warrior that has uint m_health and uint m_maxHealth attributes. I want my constructor to take parameters Warrior(uint health, uint maxHealth).

Now I've studied C++ a lot and I know all the syntax etc, but it's hard to find tutorials on how to use the stuff etc. so I don't really know how should I define health and maxHealth when health obviously can't be higher than maxHealth :/

Here are few methods I thought of:

// method 1
Warrior::Warrior(uint health, uint maxHealth) :
    m_health((health > maxHealth) ? maxHealth : health),
    m_maxHealth(maxHealth)
{}

// method 2
Warrior::Warrior(uint health, uint maxHealth) :
    m_maxHealth(maxHealth)
{
    if (health > maxHealth) {
        m_health = maxHealth;
    }
    else {
        m_health = health;
    }
}

I'm sure there are other ways too. Sorry if this is just an opinion question, but if there's a "preferred" way in C++, what would it be?

4

6 回答 6

4

如果您打算在游戏期间设置生命值,您可能需要考虑这样的事情:

Warrior::Warrior(unsigned int health, unsigned int maxHealth) :
    m_maxHealth(maxHealth)
{
    setHealth(health);
}

void Warrior::setHealth(unsigned int health)
{
    m_health = std::min(health, m_maxHealth);
}
于 2013-05-14T13:43:49.370 回答
3

这实际上更多是关于接口设计的问题,而不是实际实现。您的代码中的两个实现都是相似的,并且实现了具有广泛接口的设计,该接口允许任何参数组合。

另一种方法是在构造函数上使用契约,其中对象的行为只有在明显的约束health < maxHealth为真时才能得到很好的定义。在这种情况下,您将这样记录构造函数并实现它:

Warrior::Warrior(unsigned int health, unsigned int maxHealth)
  : m_health(health), m_maxHealth(maxHealth)
{
   assert(health <= maxHealth);
}

在我看来,这是一个更好的设计。没有充分的理由接受构造函数的错误参数。通过添加,assert您可以在开发周期的早期检测到您的类型是否超出合同使用。这对于具有多个相同类型参数的函数特别重要,因为它有助于检测您是否错误地无序传递参数。在那里使用合约会减少与maxHealth当前相同health,并且稍后您将难以弄清楚为什么Warrior比您想象的要弱......合约会告诉您论据立即失序。

于 2013-05-14T13:54:15.363 回答
3

有一种“更好”的方式,取决于你对更好的定义。(我猜你的意思是“更短”)

Warrior::Warrior(unsigned int health, unsigned int maxHealth) :
    m_health(std::min(health, maxHealth)),
    m_maxHealth(maxHealth)
{
}
于 2013-05-14T13:36:44.390 回答
2
template <typename T>
class RangeBoundValue
{
public:
    RangeBoundValue(const T _min, const T _max) : _min(_min), _max(_max) {}

    void setValue(T val)
    {
        if (val < _min) _value = _min;
        else if (val > _max) _value = _max;
        else _value = val;
    }

    T value() const {return _value;}

private:
    const T _min;
    const T _max;
    T       _value;
};

在里面使用这个助手类Warrior

class Warrior
{
...
private:
    RangeBoundValue<uint> _health;
};
于 2013-05-14T13:37:45.830 回答
2

如果不满足先决条件,我只会提出断言或抛出异常:

Warrior::Warrior(uint health, uint maxHealth) :
    m_health(health),
    m_maxHealth(maxHealth)
{
    assert(health <= maxHealth); 
    // or throw exception
    throw std::logic_error("explanation");
}

我的意思是,为调用者决定采用哪个值的合理性是什么?如果调用者将健康值大于 maxHealth - 这违反了你的类逻辑,所以我建议立即失败并通知调用者这一点。这应该是软件的问题,所以我认为隐藏它是不可取的。

异常或断言是一种显式机制,可帮助您及早发现和识别此类问题。

于 2013-05-14T13:38:50.960 回答
1

我很惊讶人们建议当构造函数的参数列表提供了 health>max_health 时抛出异常。例如,假设您有一些复活实现,其中所有生物都以固定的生命值复活,直到它们的最大生命值,在某些情况下,这超过了最大生命值,为了避免耦合,您不希望此方法必须存储每个对象的最大健康,您可以在将这些参数提交给构造函数之前添加方法来测试它,但这又使事情变得复杂。最大生命值通常作为生命值的上限来实现,并且鉴于这是一个简单的约束,与其他任何东西无关,因此战士应该有责任强制执行自己的约束。

于 2013-05-14T14:13:08.437 回答