Trying to code pure valid C++

Started by
82 comments, last by programering 16 years, 3 months ago
Quote:Original post by dmatter
If style is viciously impeding on getting things done then most programmers tend to opt for what works first and foremost; then they worry about how to make it better.

That's the kind of things which gets me stuck. I have difficulties to decide if should class or struct, pointer array or vector in the object definitions.

And I thought my identifier prefixes was the standard, e.g. Hungary notation.
Advertisement
Quote:Original post by dmatter
Class definitions? Yes.
Definitions in the sense that you seem to be talking about them? Not really - enlighten us.

It's not clear what you're trying to achieve here.
I think the problem is you're not really using terminology particularly well and it's somewhat confusing.

I don't mean class definitions, I mean object definitions you know in games; As monster definitions as an example, first you load the monster definitions which are the different types of monsters defined, then in your monster object(class or struct) decide which monster type definition you want that instance to be.
I mean object definitions as in game elements.

As you can see I have very hard to formulate me well.
Quote:That's the kind of things which gets me stuck. I have difficulties to decide if should class or struct, pointer array or vector in the object definitions.
As you become more familiar with C++ and programming idioms, as well as gain experience, you will have less trouble making such decisions.

Quote:And I thought my identifier prefixes was the standard, e.g. Hungary notation.
There is no standard. In reality, there are a number of good sense rules and guidelines, as well as common idioms that other C++ programmers recognize.
Hungarian notation is a recognized idiom. It is not good sense though. It’s a blight really, as the naming scheme it suggests does not help human readers. Hungarian notation was created to indicate clearly the type of something. It’s effectively useless in my book. As a programmer, your biggest problem is figuring out the *purpose* of something. If you have trouble figuring out whether something is an int or a C string or a class, then it’s not the naming convention that is in trouble. Either your programming language of choice has some serious underlying problems, or you simply don’t know the language.
That is the thing about hungarian notation most people got it wrong. Here is an interesting article explaining how to do it right.
http://www.joelonsoftware.com/articles/Wrong.html
Quote:Original post by programering
Quote:Original post by dmatter
If style is viciously impeding on getting things done then most programmers tend to opt for what works first and foremost; then they worry about how to make it better.

That's the kind of things which gets me stuck. I have difficulties to decide if should class or struct, pointer array or vector in the object definitions.

We'll there are no hard and fast rules, it's entirely up to you whether to use class or struct, in the end a struct is a class.
Here's the rules of thumb I use:

If its going to have: methods (incl. constructor or destructor), private or protected data, or in someway require inheritance - then use a class.

If not, then it's only going to contain public data, if this is actually meant to represent only data (state) then I use a struct, otherwise I'm doing something more complicated and it depends on exactly what I'm doing.

Quote:And I thought my identifier prefixes was the standard, e.g. Hungary notation.

It's not the standard, it's just a convention. The actual standard C++ library doesn't use that convention, I don't use that convention and neither does jyk.
I use a convention known as CamelCase:
UpperCamelCaseForClassNames
lowerCamelCaseForMethods()
lowerComelCaseForVariables
BUT_ALL_CAPS_SEPARATED_BY_UNDERSCORES_FOR_CONSTANTS.

Quote:I don't mean class definitions, I mean object definitions you know in games; As monster definitions as an example, first you load the monster definitions which are the different types of monsters defined, then in your monster object(class or struct) decide which monster type definition you want that instance to be.
I mean object definitions as in game elements.

Ah, I understand now [smile]

What you may want to look into are some creational design patterns, like the abstract factory, factory method, builder and prototype patterns. Found a good link.

Choosing the subjectively 'best' method does sort of depend on how you want to interface with this system and how many entites you might have, with what sort of control you want/need.
Based on the code you've presented so far, you seem to want to be able to create different types of entities from a single object description; so one way you might want to tackle this could be as so:

You have a structure that can fully describe how to initialise an entity instance:
struct EntityDescriptor{    std::string name;    std::vector<Bitmap*> bitmaps;    std::vector<Sequence*> sequences;    EntityController* controller;    /*        Anything else here    */};

N.B. You might want to consider using smart pointers or references instead of raw pointers, otherwise you need to decide who owns the actual instances.

Now you also have an entity class that can construct itself from a description by taking the attributes it's interested in:
class Entity{public:    // Take whatever attributes we care about for construction.    Entity(const EntityDescriptor& desc);    void update();    void render();private:    std::string name;    Bitmap image;    EntityController& controller;    vector2D position;    float angle;    // etc};Entity::Entity(const EntityDescriptor& desc){    name = desc.name;                              // copy the name    image = desc.bitmaps[ 0 ];                     // take the 1st bitmap    controller = *desc.controller;                 // take the controller    position = controller.getStartPosition();      // take the start position    angle = controller.getStartOrientation();      // take the start angle}


Then you have a factory that can create actual scene entities.
Here's a simple one (with no error checking), it stores prototypes of EntityDescriptors and when an entity needs to be created it finds the suitable description and passes that to the entity's constructor:
class EntityFactory{public:    // Adds a description of an entity to the factory.    void addEntityDescriptor(const EntityDescriptor& desc);    // Creates and returns a new entity instance.    Entity* create(const std::string name);private:    std::map<std::string, EntityDescriptor> entityDescriptions;};void EntityFactory::addEntityDesctiptor(const EntityDescription& desc){    entityDescriptions[desc.name] = desc;}Entity* EntityFactory::create(const std::string name){    // Pass the description to the entity's constructor (should validate that 'name' actually exists in the map.)    return new Entity( entityDescriptions[name] );}


So with all that in-place you can now add descriptors of entities to a factory at load-time and then whenver you want to spawn an entity you can ask the factory to find a suitable descriptor and construct an entity using it...

// Load the different entities that make up the game (probably from an external game file, but i've used hard-coded constants here)void Game::loadEntityDescriptors(){    this->entityFactory.addEntityDescriptor( Game::PLAYER_DESC );    this->entityFactory.addEntityDescriptor( Game::GHOST_DESC );    this->entityFactory.addEntityDescriptor( Game::TREE_DESC );    this->entityFactory.addEntityDescriptor( Game::WATER_DESC );    //    // Create a forest description    //    // Take the generic tree descriptor as a starting point    EntityDescriptor forestDesc = Game::TREE_DESC;    // Replace the tree controller with a forest controller    forestDesc.controller = &this->forestController;    // Change the name    forstDesc.name = "Forest";    // The density of trees in the forest    this->forestController.setTreeDensity(10);    // Add to the factory    this->entityFactory.addEntityDescriptor( forestDesc );}// Create a forestvoid Game::spawnForest(){    this->forestController.randomiseSpawnPoint();    this->entityPool.add( this.entityFactory.create("Forest") );}



Well there you go. Hopefully that gives you some ideas.

One other thing worth mentioning - with the configuration of entities, their logic (the controllers) and their representation (bitmaps), you can look into the Model-View-Controller (MVC) idiom. The code I presented above is based on the snippets you provided, better use of the MVC pattern can lead to a cleaner and more flexible design overall [smile]

[Edited by - dmatter on January 1, 2008 6:27:05 PM]
Quote:Original post by oler1s
Quote:That's the kind of things which gets me stuck. I have difficulties to decide if should class or struct, pointer array or vector in the object definitions.
As you become more familiar with C++ and programming idioms, as well as gain experience, you will have less trouble making such decisions.


There. Best answer so far IMHO.

Oler1s, browse some other's c++ code and see how they wrote their code. Then you'll have some references on how things can be done. If you have doubts about why someone made something in a specific way, come here and ask.
There are lots of Open Source games written in C++ that you can download the source code.
[size="2"]I like the Walrus best.
Quote:Original post by stonemetal
That is the thing about hungarian notation most people got it wrong. Here is an interesting article explaining how to do it right.
Making Wrong Code Look Wrong


Thanks for the link! (Linkified for those lazy people) Very interesting and helpful read.
Quote: Oler1s, browse some other's c++ code and see how they wrote their code.
You sure you got the right person there? ;)
Quote: That is the thing about hungarian notation most people got it wrong.
People may have misunderstood the purpose or implementation of HN. However, even “correctly” implemented, it still pushes bad code.

This is the fundamental idea HN pushes: you have a variable that needs to encode information about what context it should be used in. Since you can freely choose the name of the variable, name it in such a way that this extra information is evident. For the sake of brevity, come up with some abbreviations to tack on as a prefix to each variable, to indicate the context to be used in.

But there’s something fundamentally wrong about this approach. If for example, you have two strings, one containing unchecked user data and one containing checked data, you should not be manually naming each string to reflect what it should contain. It’s unmaintainable, bad design. What if you switch third party libraries; the new one guarantees incoming user data to be valid and safe. Are you going to go around renaming all your variables? The other way around? Besides, your code starts looking like machine generated code with all the prefixes.

No. If you are forced to play name games with variables to write good code, then your language has fundamental problems. Early C had problems. Modern C++ has features and idioms to properly deal with the need to indicate information about the variable. But you know what relying on HN does? Aside from writing bad code, you don’t bother to take advantage of the tools available to you. Tools that make your life and other programmers’ who have to read your code, much better.
This is precisely why I warn beginners in C++ to stay away from HN and similar notations.
Initializer lists!

Instead of:
Entity::Entity(const EntityDescriptor& desc){    name = desc.name;                              // copy the name    image = desc.bitmaps[ 0 ];                     // take the 1st bitmap    controller = desc.controller;                  // take the controller    position = controller.getStartPosition();      // take the start position    angle = controller.getStartOrientation();      // take the start angle}


do:

Entity::Entity(const EntityDescriptor& desc)  : name( desc.name );                              // copy the name  , image( desc.bitmaps[0] );                       // take the 1st bitmap  , controller( desc.controller );                  // take the controller  , position( controller.getStartPosition() );      // take the start position  , angle( controller.getStartOrientation() );      // take the start angle{}


While it remains arguable about which is better, there is a certain aspect that initializer lists cover, namely const variables.

Consider this:
class Foo {  Foo( const std::string & s ) : name( s ) {}  Foo( const Foo & other ) : name( other.name ) {}  Foo operator=( const Foo &other ) {    return Foo(other);  }  const std::string s;};


It's impossible to such things with assignments, since assignment operator doesn't work on const variables.
Now I got loose, now I know how I shall do.

But I have encountered this error I don't know how to deal with about string reference parameter:
game.cppf:\projects\programming\klingis entertainment\klingis 2d\sdl - 1 sprite\pure c++\game.cpp(24) :error C2664: '__thiscall CFile::CFile(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &)' :cannot convert parameter 1 from 'char [9]' to 'class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &'        A reference that is not to 'const' cannot be bound to a non-lvalue


In:
CFile *pFile = new CFile("heil.bmp"); // <-- THIS LINE !!!


The CFile constructor declarations:
CFile(std::string &rFilename);



This is what I have written now, the file source code: "filesrc.h"
#ifndef _FILESRC_H#define _FILESRC_Hvoid InitDir();enum FILEDIR{	FILEDIR_APP,	FILEDIR_DATA,	FILEDIR_MAPS,	FILEDIR_FONTS,	FILEDIR_TILES,	FILEDIR_TEXTURES,	FILEDIR_SCREENS,	FILEDIR_ENTITIES,	FILEDIR_SOUNDS,	FILEDIR_MUSIC,	FILEDIR_SAVES,	NUM_FILEDIRS};#include <string>class CFileDir{public:	CFileDir(std::string &rName, FILEDIR parent);	static std::string &GetDirPath(FILEDIR dir);private:	static CFileDir **s_pFileDirs;	static std::string s_dirpath;	std::string m_name;	FILEDIR m_parent;};class CFile{public:	CFile(std::string &rFilename);	bool SetDir(FILEDIR dir);	void SetDir(std::string &rDirname);	void AddDir(std::string &rDirname);	SDL_RWops *Open(char *mode);	SDL_RWops *Open(std::string &rFilepath, char *mode);	SDL_RWops *GetRWops();	void Close();	~CFile();private://	FILEDIR m_filedir	std::string m_filename;	std::string m_dirpath;	std::string m_filepath;	SDL_RWops *m_pRWops;};#endif


"filesrc.cpp"
#include "filesrc.h"CFileDir *g_pFileDirs[NUM_FILEDIRS];void InitDir(){	g_pFileDirs[FILEDIR_APP] = NULL;	g_pFileDirs[FILEDIR_DATA] = new CFileDir("../../../Data",FILEDIR_APP );	g_pFileDirs[FILEDIR_MAPS ] = new CFileDir("Maps"        ,FILEDIR_APP );	g_pFileDirs[FILEDIR_FONTS ]	= new CFileDir("Fonts"		,FILEDIR_DATA);	g_pFileDirs[FILEDIR_TILES  ] = new CFileDir("Tiles"		,FILEDIR_DATA);	g_pFileDirs[FILEDIR_TEXTURES] = new CFileDir("Textures" ,FILEDIR_DATA);	g_pFileDirs[FILEDIR_SCREENS	] = new CFileDir("Screens"  ,FILEDIR_DATA);	g_pFileDirs[FILEDIR_ENTITIES] = new CFileDir("Entities" ,FILEDIR_DATA);	g_pFileDirs[FILEDIR_SOUNDS ]  = new CFileDir("Sounds"	,FILEDIR_DATA);	g_pFileDirs[FILEDIR_MUSIC ]	  = new CFileDir("Music"	,FILEDIR_DATA);	g_pFileDirs[FILEDIR_SAVES]	  = new CFileDir("Saves"	,FILEDIR_DATA);	CFileDir::s_pFileDirs = g_pFileDirs;}CFileDir::CFileDir(std::string &rName, FILEDIR parent){	m_name = rName;	m_parent = parent;};CFileDir **CFileDir::s_pFileDirs;std::string CFileDir::s_dirpath;std::string &CFileDir::GetDirPath(FILEDIR dir){	CFileDir *pFileDir = s_pFileDirs[dir];	s_dirpath = "";	while (pFileDir)	{		FILEDIR parent = pFileDir->m_parent;			//	if (parent < NUM_FILEDIRS)	//	{			s_dirpath += pFileDir->m_name + '/';			pFileDir = s_pFileDirs[parent];	//	}	}	return s_dirpath;}CFile::CFile(std::string &rFilename){	m_filename = rFilename;//	m_directory = FILEDIR_APP;	m_dirpath = new std::string;}bool CFile::SetDir(FILEDIR dir){	m_dirpath = CFileDir::GetDirPath(dir);}void CFile::SetDir(std::string &rDirname){	m_dirpath = rDirname + '/';}void CFile::AddDir(std::string &rDirname){	m_dirpath += rDirname + '/';}SDL_RWops *CFile::Open(char *mode){	m_filepath = m_dirpath + m_filename;	return Open(m_filepath,mode);}SDL_RWops *CFile::Open(std::string &rFilepath, char *mode){	fprintf(stdout,"Opening file: \"%s\"\n",rFilepath);	m_pFile = SDL_RWFromFile(rFilepath,mode);	return GetRWops();}SDL_RWops *CFile::GetRWops(){	return pRWops;}void CFile::Close(){	SDL_RWclose(pRWops);}~CFile::CFile(){	Close();	SDL_FreeRW(pRWops);}



I'll get rid off that hungary notation in a later version.


And that's real nice demonstrations, dmatter. By the way are you the author of Dark Matter?

This topic is closed to new replies.

Advertisement