C++ Workshop - Project 2

Started by
94 comments, last by Warlord_Shaun 16 years ago
Quote:
2) Instead of a direct type-query like GetItemType(), create capability queries, like isWieldable() which might return true for Weapons and false for all other Items. This gives you extra flexibility, because you could e.g. make Potions, Wands and Scrolls - but not Armor or Weapons - be isHoldable(). Sometimes, this points to a need to add a level to the inheritance hierarchy (i.e. HoldableItem), but sometimes it's not that clear-cut. But if you can avoid this approach (in favour of a better-designed inheritance scheme and/or the build-in RTTI with dynamic_cast), then you probably should avoid it. It often quickly degenerates into implementing your own RTTI instead of letting the language do it for you, and anyway you usually want member functions to do stuff(TM) when possible.

Note that the capability to use an item is perhaps the most generic method of all. A potion, when used, will apply its affect to the user. When you use armor, it typically means you are equiping it. When you use a weapon it has a dual meaning in that you can either be equiping it, or actually using it. In this case I think the former meaning is more appropriate. So now, instead of having "isHoldable" and such, you have simply an interface that has a Use method. Now, this method can be customized for the derived types to perform the action expected, and the default implementation would simply inform the user that the item is not usable (such as random treasure, quest items that have to purpose, etc.)

At the same time we must think about other interesting possibilities, such as dual weilding. In such a case, how does one determine which is the off hand weapon and which is the primary? This would require a change to the above design, perhaps.

Then there are monsters. How does one determine the stats of a monster? Can a monster not use items? I would hope that they would, since that certainly makes it much easier to implement some features, for instance:

"The goblins of Ar'Gathul typically are found in groups of 10 to 15 males, ruled over by a large female. The males typically carry wooden bucklers, and a short sword. The females are usually warrior and typically weild a long sword. Due to the small stature of the average goblin, the female weilds the long sword as though it were a great sword. The long sword usually been enchanted with a weak fire spell. Some females, though, do not have a particularily strong magical aptitude and thus weild regular short swords."

Now, would we really want to build two different types of Ar'Gathul goblin females? One with an enchanted fire sword and one without? Nah... probably not. Instead we would rather have a long sword with an enhancement and simply equip some with that long sword and others with a regular long sword.

Just a few thoughts.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

Advertisement
Quote:Original post by Deyja
Of course it does. It's in header '<list>', and it's called 'std::list'.


Actually, it's in <slist> and is called std::slist. std::list is a doubly linked list (with reverse iterators and so on). While most people use it, slist is still preferable in most cases (less overhead). Of course, you'll lack some std::list features, but since most of the time you do only forward loops over your container, you don't use them anyway.

@Darkstrike: to be able to handle different items in a single container, one can also use the visitor pattern. While speaking about design pattern might seems to be advanced in this case, the simplicity of the visitor makes it suitable for this project. If you want to have more informations about the underlined keywords, throw up wikipedia.

<advanced>
The goal of the vistor pattern is to implement a functionality that C++ lacks, and which is called double dispatch (C++ implements only single dispatch). The "dispatch" word here refers to how the compiler or the run time system chose the function that have to be called, depending on the polymorphic types that are used in the function call (the object whose the called function pertains to is the first polymorphic type, and the function parameters are the other ones).
For example:
class A // polymorphic type{public:  virtual void poo();};class B{  std::vector<A*> list_of_A;  virtual void do_sthg_with_a_A(A *polymorphic_object);  public:  void do_sthg_with_the_list_of_A()  {    for(size_t i=0; i<list_of_A.size(); ++i)    {      do_sthg_with_a_A(list_of_A);    }  }};class A1: public A{ // blah blah};class A2: public A{ // blah blah}class B1: public B{ // blah blah}class B2: public B{ // blah blah}

Here, A and B both define a virtual function, thus they are polymorphic types. We inherit A and B to creates the classes A1, A2, B1, B2.
Now, our problem is the following: how can we make B1 instances work only with instances of A1 (and respectively, B2 instances should only work with A2) when we call function do_sthg_with_a_A()? This is a typical case where double dispatch would be needed, but in this case the language cannot help us because the called do_sthg_with_a_A(), which can be overriden in B1 and B2, is only dependant of B1 or B2 (and not of any A derived class) - this is the single dispatch limitation of C++.
</advanced>
To overcome this limitation, we simply throw another classe in this design: the visitor class:
// forward declarationsclass A1;class A2;// visitor classclass Visitor{public:  virtual void accept(A1 *a) = 0;  virtual void accept(A2 *a) = 0;  // and so on...};

We also need to add a virtual visit() medthod to the class A. It is important to keep this method abstract (=0) and to implement it only in the child classes of A (ie. A1 and A2 in our case). The reason for this is that we want the compiler to know the concrete type of the "this" pointer, not its abstract type.
class A{public:  virtual void visit(Visitor *visitor) = 0;};class A1: public A{public:  virtual void visit(Visitor *visitor)  {    // the "magic" is here. The compiler knows that the type of this    // is A1*, and thus will call the correct Visitor::accept() function.    visitor->accept(this);  }}class A2: public A{public:  virtual void visit(Visitor *visitor)  {    visitor->accept(this);  }}

The Visitor class is typically an abstract class, and need to be derived to in order to do its job. For example, we can implement a A1 visitor in this way:
class VisitorA1: public Visitor{public:  virtual void accept(A1 *a)   {     // we know we are handling a A1 object, let's call it's poo() function    a->poo();   }  virtual void accept(A2 *a)   {     // do nothing!  };};class B1: public B{  virtual void do_sthg_with_a_A(A *a)  {    VisitorA1 visitor;    a->visit(&visitor);  }};


Let's have a look globally on what is done: first, I create a B1 object, which inherit B. Then, I call it's do_sthg_with_the_list_of_A() method, which will run through the list of A* objects and call B1::do_sthg_with_a_A().
In this method, we create a A1 visitor, and we visit() our "a" object. The A1::visit() method will call VisitorA1::accept(A1 *a) (which will in turn call a->poo()), while A2::visit() will call VisitorA1::accept(A2 *a), which does nothing.

We can apply the same technique on items:
class ItemVisitor{public:  virtual void accept(Weapon *w) = 0;  virtual void accept(Armor *a) = 0;  virtual void accept(Potion *p) = 0;  // ...};class Item{public:  virtual void visit(ItemVisitor* v) = 0;};class Weapon: public Item{public:  virtual void visit(ItemVisitor* v) { v->accept(this); }};class Inventory{  std::vector<Item*> items;public:  void equip_all_armors()  {    // this special visitor only implements accept(Armor*)    // and is used to equip all armors in the item list.     ItemVisitorEquipArmor visitor;    for (size_t i=0; i<items.size(); ++i)    {      items->visit(&visitor);    }  }};

Voilà.
The same technique can be used to perform other actions as well.

HTH,
Quote:Actually, it's in <slist> and is called std::slist. std::list is a doubly linked list (with reverse iterators and so on). While most people use it, slist is still preferable in most cases (less overhead).


He asked for a 'linked list', not a 'singly linked list'.

slist is not standard, and thus is not preferable.
Quote:Original post by Deyja
Quote:Actually, it's in <slist> and is called std::slist. std::list is a doubly linked list (with reverse iterators and so on). While most people use it, slist is still preferable in most cases (less overhead).


He asked for a 'linked list', not a 'singly linked list'.

slist is not standard, and thus is not preferable.


My bad. I used it, and never assumed that it was non standard. I just checked and it is indeed not standard. Sorry for the misleading comment - I apology.
Does VC++ .NET 2003 have a bug in its implementation of the standard library? I can't get std::cout to emit a std::string object; eg:

#include

int main()
{
std::string temp = "Hello world!";
std::cout <
Does VC++ .NET 2003 have a bug in its implementation of the standard library? I can't get std::cout to emit a std::string object; eg:

#include

int main()
{
std::string temp = "Hello world!";
std::cout <
Quote:Original post by Anonymous Poster
Does VC++ .NET 2003 have a bug in its implementation of the standard library? I can't get std::cout to emit a std::string object; eg:


I'm using the Microsoft Visual C++ 2005 Edition which does not have any problems. But I can't imagine there is a problem with doing something this basic. Can you post your entire example?

Well, the forums obviously have a problem with displaying the left and right tag characters.

To the AP: Make sure you're doing two left things, not >>. And most definitely not a left one and a >. [wink]

EDIT: Also, to the AP: Use the source tags, like so:

#include <iostream>using namespace std;int main(){    cout << "Hello, World!";    return 0;}
The forums tend to eat anything between less-than and greater-than when they aren't inside source tags. These forums allow html. There's no robust way to distinquish between an html tag and, say, a template parameter list - that's why we have source tags.

Eitherway, std::cout should handle an std::string just find. Is it a compilation error? Does it output the wrong string? Be more specific, and give us the actual code, inside source tags.
I have a question relating to the workshop as a whole - what will happen after we complete the book?

This topic is closed to new replies.

Advertisement