Storing and accessing tile properties in C++ - c++

I am building a tile engine in C++. What is the most efficient way to store the logical properties of the individual tiles in the game? I understand the rendering side of the program, but I am having trouble taking a simple id number and turning it into an actual set of properties ( like whether or not a tile is walkable or flammable or can trigger an event, etc. )
One idea is to have a tile object that has the potential to be any kind of tile, and turns on certain boolean "switches" based on the type ( note that the following is mostly just pseudocode and not meant to actually compile):
class Tile
{
private:
int m_type;
bool m_walkable;
// etc...
public:
Tile( int type ) : m_type( type)
{
if( type == 0 )
{
m_walkable = true;
} else if( type == 1 ) {
m_walkable = false;
}
// etc etc would probably be a switch
// statement but you get the idea
}
};
Personally, I do not like this idea; I think it would be much more elegant for each type of tile to have its own data structure. I imagine using some kind of inheritance based system but I just can't seem to put it all together. Fundamentally, I think it should look something like this:
enum class TileType
{
TILE_TYPE null, // 0
TILE_TYPE floor, // 1
TILE_TYPE wall, // 2
// etc ...
};
class BTile
{
private:
// Location and dimensions of tile
int m_xOffset;
int m_yOffset;
int m_width;
int m_height;
// Type of tile, initialized to 0 for base class
TileType m_type;
public:
// ...
};
class Floor : public BTile
{
private:
TileType = 1;
bool walkable = true;
// etc...
};
class Wall : public BTile
{
private:
TileType = 2;
bool walkable = false;
};
Something like this would feel much more organized and flexible, while also allowing me to plug Floor and Wall objects into any kind of function expecting a Tile object. The problem is that I just cannot seem to put this all together in a way that actually works - for example, how can I provide a specific tile type with the tile it is associated with? If I am reading a text file into my program for example, how can I get from 001 to Tile->Floor? Any advice or input on this subject would be greatly appreciated.

Try using a factory method.
The simplest way to do this is to use a switch.
Tile* createTile(TileType tileType) {
switch(tileType) {
case TileType.floor: return new Floor;
case TileType.wall: return new Wall;
}
return nullptr;
}
This is usually not recommended as you have to update the factory each time you add a new type.
Alternatively you could use some trick to register the types to the factory.
One approach is the one described here. There are a lot more strategies.

Why are you reinventing OO? Objects already have a type, no need for TileType. You may want to read up on virtual functions.

There are several approaches here, depending on what exactly you want the tiles in your game to do. You can go the object oriented way, by having distinct classes for the different tile types that you have, or you can go simpler and just have a bitset that represent the different abilities your tiles will have.
This choice will depend on what you want to do.
Bitset only
Oftentimes, the bitset-only variant is enough. To do that you'll need something along those lines:
You most probably want a set of flags which will represent different abilities of your tiles (e.g IsWalkable, IsWater, etc). Something similar to this:
struct TileFlag
{
bool m_IsWalkable : 1;
bool m_IsWater : 1;
//other flags you might need
};
With this in mind, your Tiles would be something along those lines (Texture and Texture manager are there just for the example):
struct Tile
{
void Render();
void Serialize(const boost::property_tree::ptree& tileData)
{
m_Flags.m_IsWalkable = tileData.get<bool>("walkable", false);
m_Flags.m_IsWater = tileData.get<bool>("water", false);
std::string texturePath = tileData.get<std::string>("texturePath", "");
m_TileTexture = TextureManager::GetOrLoad(texturePath);
}
TileFlags m_Flags;
std::shared_ptr<Texture> m_TileTexture;
};
You would need some kind of registry, where you contain all of your tiles, so that your levels can reference the tiles. This registry can be as simple as an std::map.
Some example code:
struct TileRegistry
{
void LoadTiles(const boost::property_tree::ptree& tiles)
{
for (boost::property_tree::ptree::value_type& tileType : tiles.get_child("tileTypes"))
{
std::unique_ptr<Tile> newTile = std::make_unique<Tile>();
newTile->Serialize(tileType.second);
m_Tiles[tileType.first] = std::move(newTile);
}
}
Tile* FindTile(const std::string& tileType)
{
Tile* result = nullptr;
auto search = m_Tiles.find(tileType);
if (search != m_Tiles.end()) {
result = search->second.get();
}
return result;
}
std::map<std::string, std::unique_ptr<Tile>> m_Tiles;
};
Then, when you load your levels, you just search for the Tile Type in the TileRegistry, and you'll get a pointer to your Tile.
OOP Style object inheritance
This approach would borrow a lot from the previous one, with the biggest difference being in how you are going to create your tiles. You are going to need some kind of Factory, as #artcorpse mentions.
If you want to go a bit more generic, you can do some automation magic with a few macros:
struct TileFactory
{
static std::map<std::string, CreateFunctionPtr> m_FactoryFunctors;
std::unique_ptr<ITile> CreateTile(const std::string& tileType)
{
std::unique_ptr<ITile> result;
auto search = m_FactoryFunctors.find(tileType);
if (search != m_FactoryFunctors.end()) {
auto creationFunctionPtr = search->second;
result = creationFunctionPtr(); //Notice the function invocation here
}
return result;
}
};
template<typename T>
struct TileRegistrator
{
TileRegistrator(const std::string& tileTypeName){
TileFactory::m_FactoryFunctors[tileTypeName] = &T::CreateTile;
}
};
#define DECLARE_TILE_TYPE(TileType) \
static std::unique_ptr<ITile> CreateTile() { return std::make_unique<TileType>();} \
static const TileRegistrator<TileType> s_Registrator;
#define DEFINE_TILE_TYPE(TileType) \
const TileRegistrator<TileType> TileType::s_Registrator = {#TileType};
And how you use those macros:
struct ITile
{
virtual ~ITile() = default; //Don't forget a virtual destructor when you have object which can be accessed by pointer to Base!
virtual bool IsWalkable() const = 0;
virtual bool IsSailable() const = 0;
virtual void Serialize(const boost::property_tree::ptree& tileData) = 0;
};
In your .h files, e.g. OceanTile.h:
struct OceanTile : public ITile
{
DECLARE_TILE_TYPE(OceanTile);
bool IsWalkable() const override;
bool IsSailable() const override;
void Serialize(const boost::property_tree::ptree& tileData) override;
int m_WavesIntensity{0};
};
In your .cpp files, e.g. OceanTile.cpp:
DEFINE_TILE_TYPE(OceanTile)
bool OceanTile::IsWalkable() const {
return false;
}
bool OceanTile::IsSailable() const {
return true;
}
void OceanTile::Serialize(const boost::property_tree::ptree& tileData) {
m_WavesIntensity = tileData.get<int>("WavesIntensity", 0);
}
And creating a new tile object, asuming you know its type as a string (e.g. coming from a data file is very simple:
void LoadTiles(const boost::property_tree::ptree& levelData)
{
for (boost::property_tree::ptree::value_type& tile : levelData.get_child("levelTiles"))
{
std::unique_ptr<ITile> newTile = TileFactory::CreateTile(tile->first);
newTile->Serialize(tile.second);
//Do whatever you want to do with your Tile - maybe store it in some vector of all tiles for the level or something
}
}
Disclaimer: I have not compiled or tested any of the above code, but hopefully it can give you an idea on how to get started. There might be any number of bugs hiding there.
I encourage you to start with the Bitset only option, as this is enough for a lot of different types of games, and is much simpler to work with and reason about.
Hope this gives you some start :)

Related

Impossible to store cocos2d::Animation * in a std::vector of structure?

I work on a project made with cocos2d-x framework (c++).
In my Player class, I have to manage the animations.
Iinitially I had this code that worked without any problem:
First, the animation object is a cocos2d Class cocos2d::Animation. Just remember that this object contains a cocos2d::Vector<AnimationFrame*> _frames; member.
Doc: http://www.cocos2d-x.org/reference/native-cpp/V3.5/d3/dc5/classcocos2d_1_1_animation.html#a0fdc0be158df7e09d04644be353db056
class Player : public cocos2d::Sprite {
private:
cocos2d::Map<std::string, cocos2d::Animation*> animations;
cocos2d::Vector<cocos2d::SpriteFrame*> getAnimation(const char *format, int frameStart, int count);
void update(float delta) override;
bool init() override;
public:
static Player* create();
bool init() override;
//...
};
And the implementation side:
bool Player::init() {
//...
animations.insert("idleN", Animation::createWithSpriteFrames(getAnimation("%04d", 207, 9), 0.1));
//...
}
Vector<SpriteFrame*> Player::getAnimation(const char *format, int frameStart, int count) {
auto spriteCache = SpriteFrameCache::getInstance();
Vector<SpriteFrame*> animFrames;
char str[100] = {0};
for (int i = 1; i <= count; i++)
{
sprintf(str, format, frameStart);
log("%s", str);
animFrames.pushBack(spriteCache->getSpriteFrameByName(str));
frameStart++;
}
return animFrames;
}
//later in the code execution
void Player::manageIdle() {
auto idleAnim = Animate::create(animations[0].anim);
runAction(idleAnim);
}
You can see each Animation is contained in cocos2d::Map<std::string, cocos2d::Animation*> and as I say before, this code worked perfectly, no error.
But I needed some more informations in addition to the name and the object itself so I decided to use a structure to store all infos for each animation. And I replaced the cocos2d::Map<std::string, cocos2d::Animation*> by std::vector<animData> with animData as structure. I refactored the code like so:
class Player : public cocos2d::Sprite {
public:
typedef struct animation {
std::string name;
cocos2d::Animation* anim;
//all others info needed, not relevant here, (just several enum type variables)
} animData;
private:
std::vector<animData > animations; //the new container
//rest of code stay unchanged
};
The changes in the implementation side:
bool Player::init() {
//...
animations.push_back({"idleN", Animation::createWithSpriteFrames(getAnimation("%04d", 207, 9), 0.1)});
//no problem here...
}
But now, when I try to create a new anim with a animation saved in my container (vector) I get a SegV on this line:
void Player::manageIdle() {
auto idleAnim = Animate::create(animations[0].anim); //SegV here, in Animate::create() funct
runAction(idleAnim);
}
After search, I find that each structure member anim which is type of cocos2d::Animation*, now conatains a empty cocos2d::Vector<AnimationFrame*> _frames; and there is the problem !
It’s as if they lose the cocos2d::Vector<AnimationFrame*> ref or something like that.
So my question is why cocos2d::Vector<AnimationFrame*> become empty with my refactored code and not whith the previous one ?
I found this with test like that:
auto test = animList[0].anim->getFrames();
if (test.empty()) {
log("empty"); //The test output empty
}
Debugguer screen in the end of the init() funct:
Debugguer screen in Player::manageIdle() funct:
Edit: when I add animations.back().anim->retain(); right after the line to add an element in the vector, it solves the problem !
animations.push_back({"idleN", Animation::createWithSpriteFrames(getAnimation("%04d", 207, 9), 0.1)});
animations.back().anim->retain();
Because cocos2d::Animation* inherit from cocos2d::Ref, it is an auto-release object. When used inside a cocos2d container like cocos2d::Map or cocos2d::Vector, it is auto managed by the container itself. But I use a std::vector so I lose the ref I think. Something like that.
Now I need to find a way to get rid of this additional line of code because this multiple by twice my number of line here !
So new question here: How I can get rid of the fact I have to call animations.back().anim->retain(); each time I add a new element in my vector ?
You might create a wrapper around Ref, which "retains" ownership, and store this wrapper instead, sort of a std::unique_ptr e.g.
template<typename T> class RefOwner {
public:
RefOwner(T *t) : ref(t) {
ref->retain();
}
~RefOwner() {
ref->release();
}
T *operator->() { return ref; }
private:
T *ref;
};
and then use it as
struct animData {
std::string name;
RefOwner<cocos2d::Animation> anim;
//all others info needed, not relevant here, (just several enum type variables)
};
Disclaimer: I have no experience with cocos2d-x, just looked at Animation and Ref

What is the proper way to communicate between member classes?

I'm writing an RPG, and the basic code is below. Each player is supposed to have four stats: earth, air, fire, and water. The problem is this: if fire is less than zero, it should spill over into damage to earth. Here is my code so far:
//DANCE GAME
#include <iostream>
using namespace std;
class element {
public:
//get primary magnitude
int gpm(){
return pm;
};
//set primary magnitude
int spm(int x){
pm = x;
};
protected:
//primary magnitude
int pm;
};
class fire : public element {
};
class earth : public element {
};
class soul {
public:
earth e;
fire f;
};
int main () {
soul p1, p2;
p1.e.spm(5);
cout << p1.e.gpm() << endl;
p1.f.spm(5);
cout << p1.f.gpm() << endl;
return 0;
};
So, I want each element to have a unique spm() function to control its special behavior. For fire, it should have the effect of the pseudocode below:
fire.spm(int x)
pm += x
if pm < 0
x = abs(pm)
pm = 0
owner.earth -= x
Obviously it's that last line that's giving me problems. What is the proper way to accomplish such a thing?
You should pass owner as argument to method spm.
Obviously it's that last line that's giving me problems. What is the proper way to accomplish such a thing?
There is a guide line that states that you should not work on another object's data (Law of Demeter). Instead, you should propagate the changes downwards, from objects that know everyone else, to objects that know only how to handle their own data.
This means that your fire element implementation should not know that a soul has an earth element (let alone try to alter it directly). It is instead the responsibility of the soul to propagate these changes (because a soul instance knows both earth and fire classes).
Based on this, you should consider something like this:
class element
{
public:
// update primary magnitude, taking hints from the provided element
// if this->get_type() == other.get_type()
// at the end of the operation, primary_magnitude_ should be
// other.primary_magnitude_;
virtual void update(const element& other)
{
if(get_type() == other.get_type())
set_magnitude(other.magnitude());
}
protected:
enum { earth_type, fire_type, air_type, water_type } element_type;
virtual element_type get_type() const = 0;
int magnitude() const { return primary_magnitude_; }
void update_magnitude(int delta) { primary_magnitude_ += delta; }
void set_magnitude(int value) { primary_magnitude_ = delta; }
private:
int primary_magnitude_;
};
class earth
{
public:
void update(const element& other) override
{
switch(other.get_type())
{
case element::earth_type:
set_magnitude(other.magnitude());
break;
case element::fire_type:
update_magnitude( std::min(0, other.magnitude()) );
break;
}
}
protected:
element_type get_type() const override { return element::earth_type; }
};
class soul
{
public:
void update(const element& value)
{
earth_.update(value);
fire_.update(value);
}
private:
earth earth_;
fire fire_;
};
spm means "set primary magnitude". Two notes on this. First, it's better to call function SetPrimaryMagnitude or set_primary_magnitude if the function is supposed to actually set the primary magnitude. It makes life so much easier. BTW, element class has only one value, pm, so I don't see the need to call that value "primary". I mean, what would be "secondary"?
Second, you actually don't want to set that value but to have a combination of actions that increase, decrease, and spill the difference to yet another value. Maybe it would be better to call such action "adjust". And make it a member of soul (if that class is the only one that handles the math this specific way). Something like this:
soul::AdjustElementValues(element& primary, element& secondary, int value)
{
// decrease primary by value, and if needed spill the remains to secondary
}
If soul is not the only class that does this math, then move the math to separate class, which has access to all needed objects. Say, class element_spiller with constructor that takes pointers to two elements, primary and secondary, and has a method that takes int value argument and changes the elements accordingly.

Sorting a vector of a class

I have class called "UltrasoundTemplate". These UltrasoundTemplate objects contain an int parameter, which shows when they where defined (something like a time stamp). And I have a class called "UltrasoundTarget" which contains a vector of UltrasoundTemplate's.
I add UltrasoundTemplates to the vector with push_back(ultrasoundTemplate).
Now I want to sort the vector by the order of time stamps instead of the order I added them to the vector.
I found a lot of answers in google, which all show me the same solution, but obviously I'm still doing something wrong. Here are the code snippets I think are necessary for finding a solution:
ultrasoundTemplate.h
class UltrasoundTemplate
{
public:
UltrasoundTemplate(/*...*/);
int getVolumePos() { return volume_; }
private:
int volume_;
};
ultrasoundTarget.h
//the sort algorithm
struct MyTemplateSort {
bool operator() ( UltrasoundTemplate t1, UltrasoundTemplate t2){
int it1 = t1.getVolumePos();
int it2 = t2.getVolumePos();
if (it1 < it2)
return true;
return false;
}
};
class UltrasoundTarget
{
public:
UltrasoundTarget(/*...*/);
vector<UltrasoundTemplate> getTemplates() { return USTemplateVector_; }
private:
vector<UltrasoundTemplate> USTemplateVector_;
};
FMainWindow.cpp
void FMainWindow::match_slot()
{
int i;
//here I get the name of the target I'm looking for
QTreeWidgetItem *item = targetInfoWidget_->treeWidget->currentItem();
int index = targetInfoWidget_->treeWidget->indexOfTopLevelItem(item);
QString itemToAppendName = item->text(0);
for(i = 0; i < USTargetVector.size(); i++){
if(USTargetVector.at(i).getName() == itemToAppendName) {
//here I try to sort
MyTemplateSort tmpltSrt;
std::sort(USTargetVector.at(i).getTemplates().begin(),
USTargetVector.at(i).getTemplates().end(), tmpltSrt);
break;
}
}
As an example: I define Template1 in Volume(0), Template2 in Volume(70) and Template3 in Volume(40). The order now is (Template1, Template2, Template3) but I want it to be (Template1, Template3, Template2). But this code is not doing it.
If there's Information missing, just tell me and I'll provide more code.
Thanks alot.
Your getTemplates() method returns by value, making a mess here:
std::sort(USTargetVector.at(i).getTemplates().begin(),
USTargetVector.at(i).getTemplates().end(), tmpltSrt);
You are sorting an incompatible iterator range. You can fix that particular problem by returning a reference:
vector<UltrasoundTemplate>& getTemplates() { return USTemplateVector_; }
It is common practice to add a const overload to such a method:
const vector<UltrasoundTemplate>& getTemplates() const { return USTemplateVector_; }
You can also modify your comparison functor to avoid unnecessary copies (and for general readability and const correctness):
struct MyTemplateSort {
bool operator() const ( const UltrasoundTemplate& t1, const UltrasoundTemplate& t2)
{
return t1.getVolumePos() < t2.getVolumePos();
}
};
This will require that you make getVolumePos() a const method, which it should be anyway:
class UltrasoundTemplate
{
public:
...
int getVolumePos() const { return volume_; }
...
};
Note that is is not generally good practice to provide references to the private data of a class. If possible, you should find a way to remove that from the UltraSoundTarget interface. You could, for instance, expose a pair of iterators, and/or give the class a sort method.
juanchopanza answer is correct, the problem is the way you are returning the vector from UltrasoundTarget. Just to touch another topic, maybe it would be nice to change a little the designing of your implementation. As UltrasoundTarget is a container of Ultrasound's, it makes sense to implement the sort as a method of this class, this way you have direct access to USTemplateVector_ and will save unecessary copies. Something like:
class UltrasoundTarget
{
public:
UltrasoundTarget(/*...*/);
vector<UltrasoundTemplate> getTemplates() { return USTemplateVector_; }
void sort();
private:
vector<UltrasoundTemplate> USTemplateVector_;
};
void UltrasoundTarget::sort()
{
std::sort(USTemplateVector_.begin(), USTemplateVector_.end(), tmpltSrt);
}
void FMainWindow::match_slot()
{
int i;
//here I get the name of the target I'm looking for
QTreeWidgetItem *item = targetInfoWidget_->treeWidget->currentItem();
int index = targetInfoWidget_->treeWidget->indexOfTopLevelItem(item);
QString itemToAppendName = item->text(0);
for(i = 0; i < USTargetVector.size(); i++){
if(USTargetVector.at(i).getName() == itemToAppendName)
{
//here I try to sort
MyTemplateSort tmpltSrt;
USTargetVector.at(i).sort();
break;
}
}

What is the right way to switch on the actual type of an object?

I'm writing an xml parser and I need to add objects to a class generically, switching on the actual type of the object. Problem is, I'd like to keep to an interface which is simply addElement(BaseClass*) then place the object correctly.
void E_TableType::addElement(Element *e)
{
QString label = e->getName();
if (label == "state") {
state = qobject_cast<E_TableEvent*>(e);
}
else if (label == "showPaytable") {
showPaytable = qobject_cast<E_VisibleType*>(e);
}
else if (label == "sessionTip") {
sessionTip = qobject_cast<E_SessionTip*>(e);
}
else if (label == "logoffmedia") {
logoffMedia = qobject_cast<E_UrlType*>(e);
}
else {
this->errorMessage(e);
}
}
This is the calling class, an object factory. myElement is an instance of E_TableType.
F_TableTypeFactory::F_TableTypeFactory()
{
this->myElement = myTable = 0;
}
void F_TableTypeFactory::start(QString qname)
{
this->myElement = myTable = new E_TableType(qname);
}
void F_TableTypeFactory::fill(const QString& string)
{
// don't fill complex types.
}
void F_TableTypeFactory::addChild(Element* child)
{
myTable->addElement(child);
}
Element* F_TableTypeFactory::finish()
{
return myElement;
}
void F_TableTypeFactory::addAttributes(const QXmlAttributes &attribs) {
QString tName = attribs.value(QString("id"));
myTable->setTableName(tName);
}
Have you considered using polymorphism here? If a common interface can be implemented by each of your concrete classes then all of this code goes away and things become simple and easy to change in the future. For example:
class Camera {
public:
virtual void Init() = 0;
virtual void TakeSnapshot() = 0;
}
class KodakCamera : Camera {
public:
void Init() { /* initialize a Kodak camera */ };
void TakeSnapshot() { std::cout << "Kodak snapshot"; }
}
class SonyCamera : Camera {
public:
void Init() { /* initialize a Sony camera */ };
void TakeSnapshot() { std::cout << "Sony snapshot"; }
}
So, let's assume we have a system which contains a hardware device, in this case, a camera. Each device requires different logic to take a picture, but the code has to support a system with any supported camera, so we don't want switch statements littered throughout our code. So, we have created an abstract class Camera.
Each concrete class (i.e., SonyCamera, KodakCamera) implementation will incluse different headers, link to different libraries, etc., but they all share a common interface; we just have to decide which one to create up front. So...
std::unique_ptr<Camera> InitCamera(CameraType type) {
std::unique_ptr<Camera> ret;
Camera *cam;
switch(type) {
case Kodak:
cam = new KodakCamera();
break;
case Sony:
cam = new SonyCamera();
break;
default:
// throw an error, whatever
return;
}
ret.reset(cam);
ret->Init();
return ret;
}
int main(...) {
// get system camera type
std::unique_ptr<Camera> cam = InitCamera(cameraType);
// now we can call cam->TakeSnapshot
// and know that the correct version will be called.
}
So now we have a concrete instance that implements Camera. We can call TakeSnapshot without checking for the correct type anywhere in code because it doesn't matter; we know the correct version for the correct hardware will be called. Hope this helped.
Per your comment below:
I've been trying to use polymorphism, but I think the elements differ too much. For example, E_SessionTip has an amount and status element where E_Url just has a url. I could unify this under a property system but then I lose all the nice typing entirely. If you know of a way this can work though, I'm open to suggestions.
I would propose passing the responsibility for writing the XML data to your types which share a common interface. For example, instead of something like this:
void WriteXml(Entity *entity) {
switch(/* type of entity */) {
// get data from entity depending
// on its type and format
}
// write data to XML
}
Do something like this:
class SomeEntity : EntityBase {
public:
void WriteToXml(XmlStream &stream) {
// write xml to the data stream.
// the entity knows how to do this,
// you don't have to worry about what data
// there is to be written from the outside
}
private:
// your internal data
}
void WriteXml(Entity *entity) {
XmlStream str = GetStream();
entity->WriteToXml(stream);
}
Does that work for you? I've done exactly this before and it worked for me. Let me know.
Double-dispatch may be of interest. The table (in your case) would call a virtual method of the base element, which in turns calls back into the table. This second call is made with the dynamic type of the object, so the appropriate overloaded method is found in the Table class.
#include <iostream>
class Table; //forward declare
class BaseElement
{
public:
virtual void addTo(Table* t);
};
class DerivedElement1 : public BaseElement
{
virtual void addTo(Table* t);
};
class DerivedElement2 : public BaseElement
{
virtual void addTo(Table* t);
};
class Table
{
public:
void addElement(BaseElement* e){ e->addTo(this); }
void addSpecific(DerivedElement1* e){ std::cout<<"D1"; }
void addSpecific(DerivedElement2* e){ std::cout<<"D2"; }
void addSpecific(BaseElement* e){ std::cout<<"B"; }
};
void BaseElement::addTo(Table* t){ t->addSpecific(this); }
void DerivedElement1::addTo(Table* t){ t->addSpecific(this); }
void DerivedElement2::addTo(Table* t){ t->addSpecific(this); }
int main()
{
Table t;
DerivedElement1 d1;
DerivedElement2 d2;
BaseElement b;
t.addElement(&d1);
t.addElement(&d2);
t.addElement(&b);
}
output: D1D2B
Have a Look at the Visitor Pattern, it might help you

Working with enum-like data in C++

I am updating an old piece of C++ code and am stuck on a design issue and need advice on the best course of action. The code handles geometric data. Currently, the code defines many global constants to handle element types:
#define TETRAHEDRON 0
#define HEXAHEDRON 1
Each constant has information associated with it that remains constant and which is currently handled by a class, in our case Topology.
int Topology::nodesPerElement(int topType)
{
switch(topType) {
case TETRAHEDRON:
return 4;
break;
case HEXAHEDRON:
return 8;
break;
}
}
The Topology class has many of these functions that simply switch on the global constant to figure out associated information. There are a lot of element types and many bugs are introduced by switch statements that don't consider all element types. If an element type is added all of these methods need to be fixed. I need a better way of doing this that keeps the associated information with the type.
Enumerations are an improvement over this design, but it doesn't solve the problem of associating data with the enumeration.
For simplicity, I would like to avoid needing to instantiate classes for each type, as each will contain only static data that doesn't change.
What I really need is a "static class" that holds this information and performs like the pseudocode below:
class Tetrahedron : public TopType {
static const int nodesPerElement = 4;
static const std::string name = "Tet";
etc...
}
Each method in Topology becomes trivial:
int Topology::nodesPerElement(TopType topType)
{
return topType.nodesPerElement;
}
Is there a way to do this in C++? I've thought about just getting rid of the enumerations and having separate child Topology classes for each TopologyType, but the feedback I get from others is that it's too complicated of a solution. I hope that my question is clear enough.
Create a base class that contains all of the properties that your objects should support, and a private constructor to set those properties. You don't need derived classes, then: you can use static public objects to create the objects that you want with the desired properties.
class TopologyObject
{
private:
int numberVertices;
int numberFaces;
// etc.
public:
int getVertices() { return numberVertices; };
int getFaces() { return numberFaces; };
protected:
TopologyObject(int vertices, int faces) :
numberVertices(vertices),
numberFaces(faces)
{};
public:
static TopologyObject Tetrahedron = new TopologyObject(4, 4);
// etc.
}
You can access the Tetrahedron with all of its properties via TopologyObject::Tetrahedron.
If you decide that you need more complex variable behavior based on the type of object, then you really do need derived classes and virtual methods for the overrideable behavior.
Unless your Topology types have different runtime behaviors (like drawing themselves), then I agree with your peers that sub-classing is overkill. Reporting static properties like nodesPerElement and name is hardly a runtime behavior.
Unless you are not telling us the whole story about Topology, it seems that what you need is a simple property map. Use std::map to associate a topology type code with a structure of topology properties. This refactoring resembles Replace Subclass with Fields.
Here's some code that may serve as inspiration:
#include <cassert>
#include <iostream>
#include <map>
#include <string>
struct Topology
{
enum Code {tetrahedron, hexahedron};
int nodesPerElement;
std::string name;
};
namespace // Anonymous namespace
{
// Lookup table associating topology code with properties
const struct {Topology::Code code; Topology topo;} topoTable_[] =
{
{Topology::tetrahedron, {4, "Tetrahedron"}},
{Topology::hexahedron, {6, "Hexahedron"}}
};
};
class TopologyMap // Singleton
{
public:
static TopologyMap lookup(Topology::Code code)
{
return Topology(instance().doLookup(code));
}
private:
typedef std::map<Topology::Code, Topology> Map;
Map map_;
TopologyMap()
{
// Initialize map with constant property table
size_t tableSize = sizeof(topoTable_) / sizeof(topoTable_[0]);
for (size_t row=0; row<tableSize; ++row)
{
map_[topoTable_[row].code] = topoTable_[row].topo;
}
}
static TopologyMap& instance()
{
static TopologyMap instance;
return instance;
}
const Topology& doLookup(Topology::Code code) const
{
Map::const_iterator match = map_.find(code);
assert(match != map_.end());
return match->second;
}
};
class Shape
{
public:
Shape(Topology::Code topoCode)
: topo_(TopologyMap::lookup(topoCode)) {}
const Topology& topology() const {return topo_;}
// etc...
private:
Topology topo_;
};
int main()
{
Shape shape1(Topology::tetrahedron);
Shape shape2(Topology::hexahedron);
std::cout << "shape1 is a " << shape1.topology().name << " with " <<
shape1.topology().nodesPerElement << " nodes per element.\n";
std::cout << "shape2 is a " << shape2.topology().name << " with " <<
shape2.topology().nodesPerElement << " nodes per element.\n";
};
Output:
shape1 is a Tetrahedron with 4 nodes per element.
shape2 is a Hexahedron with 6 nodes per element.
If the topology code is zero-based and continuous, then you may use simple array indexing instead of a map. However, array indexing will be more error-prone if someone messes around with the topology code enum. Here is the same example that uses array indexing:
#include <cassert>
#include <iostream>
#include <map>
#include <string>
struct Topology
{
enum Code {tetrahedron, hexahedron, CODE_COUNT};
int nodesPerElement;
std::string name;
};
namespace // Anonymous namespace
{
// Lookup table associating topology code with properties
const Topology topoTable_[] =
{
{4, "Tetrahedron"},
{6, "Hexahedron"}
};
};
class TopologyMap // Singleton
{
public:
static Topology lookup(Topology::Code code)
{
assert(code < Topology::CODE_COUNT);
return topoTable_[code];
}
private:
TopologyMap() {} // Non-instantiable
};
class Shape
{
public:
Shape(Topology::Code topoCode)
: topo_(TopologyMap::lookup(topoCode)) {}
const Topology& topology() const {return topo_;}
// etc...
private:
Topology topo_;
};
int main()
{
Shape shape1(Topology::tetrahedron);
Shape shape2(Topology::hexahedron);
std::cout << "shape1 is a " << shape1.topology().name << " with " <<
shape1.topology().nodesPerElement << " nodes per element.\n";
std::cout << "shape2 is a " << shape2.topology().name << " with " <<
shape2.topology().nodesPerElement << " nodes per element.\n";
};
Note that because the details of storing and retrieving Topology was encapsulated in TopologyMap, I didn't have to rewrite any code in Shape and main.
You can have classes with nothing but static member variables. And that's a nice way to encapsulate attribute data.
If you'd rather not do that, traits might get you what you want.
I'm not sure who advised you to avoid derived classes for each Toplogy type. To my eye, this problem is screaming for derived classes.
Unless you would need a very large number of such classes.
Personally I think the best way to store this information would be to create a general Shape class. Then, instead of coding all those static variables put them in a file/database and load your shape information from the data store when you start your program.
Couldn't you use a record to do this if your goal is to avoid class instantiation?
Really though, you should class the poop out of this.
If topType is contiguous and starting a 0, you could just maintain an array of structs and index into that, instead of trying to have classes and subclasses. This way the only code change you would need is to
add the struct: Easy
add an array of structs: Easy
change each method to index into array and return proper field of struct: Tedious, but you have to do this anyway.
It your TopologyType can just be modelled as an instance of a struct (i.e no methods on it etc), Classes + Derived classes is overkill, IMO.
Since (apparently) all the relevant data is available at compile time, one possibility would be to use an enumeration along with templates and specialization to do the job:
enum { tetrahedron, hexahedron };
template <int type>
struct nodes_per_element { int operator()() const {
throw std::invalid_argument("Attempt to use unknown shape");
};
template <>
struct nodes_per_element<tetrahedron> { int operator()() const { return 4; } };
template <>
struct nodes_per_element<hexahedron> { int operator()() const { return 8; } };
You'd use this like: int x = nodes_per_element<hexahedron>()(); If you try to use it for a value for which there's no specialization, that will invoke the un-specialized template, which will throw an exception, halting the program and (normally) displaying a message saying you attempted to use an unknown shape. Of course, you can customize how that's displayed (if at all).
This should quickly show where you have problems due to values that haven't been defined.
The other obvious possibility would be to just define a struct for each shape you're going to use, and create an array of those structs, using the name of the shape as an index into the data, and the name of the specific data you want will be the member of the struct. For just the nodes per element you've given, that would look like:
struct shape_data {
int nodes_per_element;
std::string name;
};
shape_data data[] = {
{4, "Tetrahedron"},
{8, "Hexahedron" }
};
Retrieving data would be something like:
shape_data &s = data[hexahedron];
std::cout << "A " << s.name << " has " << s.nodes_per_element << "nodes per element.\n";
Having look at the previous answers, I've decided to add my own.
To me there are 2 things that I would require of such a design:
the ability to define a new item without recompiling the whole program
the ability to look up an item based on a property (like the number of faces)
This can be quite easy to do, so here is my little bit of code:
class Solid
{
typedef std::vector<Solid> solids_type;
public:
Solid(std::string name, size_t faces, size_t nodes):
mName(name), mFaces(faces), mNodes(nodes)
{
}
///
/// Properties
///
const std::string& getName() const { return mName; }
size_t getFaces() const { return mFaces; }
size_t getNodes() const { return mNodes; }
///
/// Collection Handling
///
static bool Add(Solid solid); // only add if it's not already there.
///
/// struct Predicate: std::unary_function<Solid,bool>
///
template <class Predicate>
static const Solid* Get(Predicate pred)
{
solids_type::const_iterator it =
std::find_if(Solids().begin(), Solids().end(), pred);
return it == Solids().end()) ? 0 : &(*it);
} // Get
///
/// Some Predicates
///
class ByName: std::unary_function<Solid,bool>
{
public:
ByName(std::string name): mName(name) {}
bool operator()(const Solid& s) const { return s.getName() == mName; }
private:
std::string mName;
};
class ByFaces; /// ...
class ByNodes; /// ...
private:
/// Properties
std::string mName;
size_t mFaces;
size_t mNodes;
/// Collection
static solids_type& Solids()
{
static solids_type MSolids;
return MSolids;
}
}; // class Solid
And thus, now we can have:
// in tetrahedron.cpp
namespace
{
bool gTetrahedron = Solid::Add(Solid("Tetrahedron", 4, 4));
}
// in main.cpp
int main(int argc, char* argv[])
{
const Solid* myTetra = Solid::Get(Solid::ByFaces(4));
assert(myTetra->getName() == "Tetrahedron");
assert(myTetra->getFaces() == 4);
assert(myTetra->getNodes() == 4);
return 0;
} // main
And now we have met our goals:
Adding one new solid does not cause any recompilation
We can lookup solid based on their properties
We could also imagine:
being able to iterate through all the registered solids
having them sorted by number of faces, or whatever
defining a little macro for the registration
This is precisely what virtual functions are for. The classical way to do it would be:
class Topology
{
public:
virtual int nodesPerElement() const = 0;
// etc
};
class Tetrahedrom : public Topology
{
public:
virtual nodesPerElement() const { return 4; }
// etc
}
// etc
But if you really have an aversion to re-implementing the accessor methods (as opposed to just defining variables) you could do the following with templates (although it's really no less verbose):
class Topology
{
public:
virtual int nodesPerElement() const = 0;
// etc
};
template<typename T>
class ConcreteTopology : public Topology
{
public:
virtual int nodesPerElement() const { return T::nodesPerElement; }
// etc
};
struct Tetrahedron_Data {
int nodesPerElement = 4;
// etc
};
typedef ConcreteTypology<Tetraheadron_Data> Tetrahedron;
// etc