Click to See Complete Forum and Search --> : [RESOLVED] Scalability issue


Mybowlcut
March 6th, 2008, 08:55 AM
Hey. I've got a class called Screen that is an abstraction of graphical elements (that can be drawn to the screen). The class calls this function in it's constructor:
void Screen::Load_Elements(const std::string& directory)
{
std::ifstream image_file;
IO::open_read_file(image_file, directory + IMAGES_FN);
IO::read_data_clone<XImage, std::vector<Base_XImage*> >(elements, image_file);
image_file.close();

std::ifstream button_file;
IO::open_read_file(button_file, directory + BUTTONS_FN);
IO::read_data_clone<XButton, std::vector<Base_XImage*> >(elements, button_file);
button_file.close();

std::ifstream audible_button_file;
IO::open_read_file(audible_button_file, directory + AUDIBLE_BUTTONS_FN);
IO::read_data_clone<Audible_XButton, std::vector<Base_XImage*> >(elements, audible_button_file);
audible_button_file.close();

std::ifstream draggable_button_file;
IO::open_read_file(draggable_button_file, directory + DRAGGABLE_BUTTONS_FN);
IO::read_data_clone<Draggable_XButton, std::vector<Base_XImage*> >(elements, draggable_button_file);
draggable_button_file.close();

std::ifstream dialog_box_file;
IO::open_read_file(dialog_box_file, directory + DIALOG_BOXES_FN);
IO::read_data_clone<XDialog_Box, std::vector<Base_XImage*> >(elements, dialog_box_file);
dialog_box_file.close();

std::ifstream input_box_file;
IO::open_read_file(input_box_file, directory + INPUT_BOXES_FN);
IO::read_data_clone<XInput_Box, std::vector<Base_XImage*> >(elements, input_box_file);
input_box_file.close();
}
This is the member that stores all the elements.
std::vector<Base_XImage*> elements;

The obvious problem here is that the Load_Elements function is huge... and will only get huger each time I create a new class like Slider or something. Also, I have a folder structure that looks like this:

<screen_name>
images.txt buttons.txt etc....
<name x y> <name x y> <...>
Where screen_name is a folder and images.txt and buttons.txt are text files and <name x y> is each individual respective element to load. Doing it this way means that each time I add a new class (e.g Slider), I have to add a corresponding text file in every "Screen" folder to every project that uses Screen, even if it's empty, otherwise open_read_file throws an exception to tell me the file wasn't found! I could fix this particular issue by putting try/catch around each paragraph of code in Load_Elements, but that is just dodgy...

I thought about something like this:template<typename T>
void Load_Elements_Of_Type(std::vector<Base_XImage*>& elements,
const std::string& directory, const std::string& file_name)
{
try
{
std::ifstream element_file;
open_read_file(element_file, directory + file_name);
read_data_clone<T, std::vector<Base_XImage*> >(elements, element_file);
element_file.close();
}
catch(...)
{ // Don't want to load this type.
}
}

// Inside Load_Elements:
Load_Elements_Of_Type<XButton>(elements, "directory/", "file.txt");
Load_Elements_Of_Type<XSlider>(elements, "directory/", "file.txt");
// Load every possible type.
But I don't know what happens when T doesn't derive from Base_XImage... and it's still gonna be non-scalable.

I really need to sort these problems out now before they get worse... Any suggestions?

Cheers. :thumb:

GNiewerth
March 6th, 2008, 10:04 AM
This is is rather advanced, but you can use typelists and template functions to read different objects from different files.


#include <string>
#include <vector>
#include <iostream>

using namespace std;

// this type is used to mark the end of a typelist
struct NullType
{
};

// The typelist. For nested typelists type U is a typelist again, so one can
// build typelists of any length
template<typename T, typename U>
struct TypeList
{
typedef T head;
typedef U tail;
};

// convenience macros for building typelists (up to 3 types)
#define TL1(T1) TypeList<T1, NullType>
#define TL2(T1,T2) TypeList<T1, TL1( T2 ) >
#define TL3(T1,T2,T3) TypeList<T1, TL2( T2,T3 ) >

// template function to specify the path/file for a given type.
template<typename T>
string get_file_name()
{
string strMessage = string( "Unregistered type " ) += typeid( T ).name();
cout << strMessage << endl;
throw exception( strMessage.c_str() );
}

// specialized template function to specify the path/file for int types
template<>
string get_file_name<int>()
{
return "/int/data.txt";
}

// specialized template function to specify the path/file for bool types
template<>
string get_file_name<bool>()
{
return "/bool/data.txt";
}

// specialized template function to specify the path/file for short types
template<>
string get_file_name<short>()
{
return "/short/data.txt";
}

/* The load function. Its template parameter is a typelist which is traversed
* from head to tail. A specialized template function for NullType ends the
* recursion.
*/
template<typename TypeList>
void load_files( const string& strBasePath, vector<Base*>& Elements )
{
// load typelist´s head object type
load_objects<TypeList::head>( strBasePath, Elements );

// proceed to next type
load_files<TypeList::tail>( strBasePath, Elements );
}

// Specialized template function to end typelist traversion
template<>
void load_files<NullType>( const string& strBasePath, vector<Base*>& Elements )
{
// used to end recursion
cout << "Typelist end" << endl;
}

// The loader function itself. It uses specialized template functions to
// obtain additional path information, based on the object type to load.
template<typename Type>
void load_objects( const string& strBasePath, vector<Base*>& Elements )
{
// concatenate path
string strPath = strBasePath + get_file_name<Type>();
cout << "Loading objects of type " << typeid( Type ).name() << " from file " << strPath << endl;
}

// define a typelist of desired types. This one consists of
// int, bool, short, and NullType (implicitely created by the TL1 macro)
#define ObjectTypes TL3( int,bool,short )

int main()
{
vector<Base*> Elements;

// load all typelist´s object types from c:/base
load_files<ObjectTypes>( "c:/base", Elements );
}

Mybowlcut
March 6th, 2008, 08:35 PM
This is is rather advancedThat would be an understatement and a half! :o

Before I start asking questions (you knew it was bound to happen :lol: ), could I just have one get_file_name function that returns the typeid's name concatenated with ".txt"? I'd have to change a few files around but it would make it a lot neater. I'd never thought to use/never used typeid so that's why I haven't done it already haha. This works: template<typename T>
string Get_Element_File_Name()
{
return Remove_Class_Prefix(string(typeid(T).name()) + "s.txt");
}

string Screen::Remove_Class_Prefix(std::string s)
{
const int PREFIX_LENGTH = 6;
if(s.size() <= PREFIX_LENGTH)
{ // Too small.
throw runtime_error("Missing class prefix in Remove_Class_Prefix: " + s);
}

s.erase(s.begin(), s.begin() + 6);
return s;
}

Ok... first of all the TypeList struct... all it has are two typedefs, right? And the extent of my C++ knowledge tells me that typedefs aren't variable declarations, just a way to rename a type? So how does TypeList store anything in it? That's the part that I really don't understand.

The next thing:// load typelist´s head object type
load_objects<TypeList::head>( strBasePath, Elements );I get that this is recursive...(Very clever way to end the recursion, btw!)// proceed to next type
load_files<TypeList::tail>( strBasePath, Elements );But I still don't get how it works due to the fact that I don't understand the TypeList part.. :o

Cheers. :D

Edit: Btw, your suggestion works perfectly! Only a very minimal amount of code needed to be changed as well... I never use this word but... marvelous! Haha.

GNiewerth
March 7th, 2008, 03:18 AM
Typelists carry type information only, they don´t have a value. They´re like a collection of types.

Let´s have a look at this simple typelist:


#define Types TL2( int,bool)


This macro expands to TypeList<int, TypeList<bool, NullType> > and defines the following type:


Types
+-- head // int type
+-- tail // a typelist again
+-- head // bool type
+-- NullType // termination type created by TL1


Given this type you can recursively walk the hierachy by using template functions. You need one generic template function for "real" types stored in the typelist and one specialized template function for handling the NullType (that means you have reached the end of the typelist).


template<typename TypeList>
void load_files( const string& strBasePath, vector<Base*>& Elements )
{
// load typelist´s head object type
load_objects<TypeList::head>( strBasePath, Elements );

// proceed to next type
load_files<TypeList::tail>( strBasePath, Elements );
}


This is the call stack for the load_files function:

load_files<TypeList<int, TypeList<bool, NullType> > >(...)
- load_objects<int>(...) // TypeList::head is an int
- load_files<TypeList<bool,NullType> >(...) // TypeList::tail is a typelist
- load_objects<bool>(...) // TypeList::head is a bool
- load_files<NullType>(...) // TypeList::tail is a NullType

Hope this helped a bit, feel free to ask if you have more questions.

Mybowlcut
March 7th, 2008, 08:47 AM
Typelists carry type information only, they don´t have a value. They´re like a collection of types.Surprisingly enough I understood the recursion part haha. Thanks for the explanation of that though I understand it now... it's a very strange concept. Does it get used in commercial code often?

Cheers.

GNiewerth
March 7th, 2008, 09:22 AM
To be honest: I was quite puzzled after reading the typelist chapter in Modern C++ Design and wondered if it has any real world purpose. It took me about one year to find a reasonable use for it, and it was only because the framework I used had some very strange restrictions.
Your Load_Elements_Of_Type function does nearly the same, without being that complex and difficult to understand. In my opinion it makes no practical difference if you add a new Load_Elements_Of_Type function call or append a new type to an existing type list. The first approach requires one new line of code, the latter one word only. I was probably too excited about typelists ;)
However, it´s always interesting to toy around with the language and discover new ways of using it.

Mybowlcut
March 7th, 2008, 07:34 PM
To be honest: I was quite puzzled after reading the typelist chapter in Modern C++ Design and wondered if it has any real world purpose. It took me about one year to find a reasonable use for it, and it was only because the framework I used had some very strange restrictions.
Your Load_Elements_Of_Type function does nearly the same, without being that complex and difficult to understand. In my opinion it makes no practical difference if you add a new Load_Elements_Of_Type function call or append a new type to an existing type list. The first approach requires one new line of code, the latter one word only. I was probably too excited about typelists ;)
However, it´s always interesting to toy around with the language and discover new ways of using it.
Ahhh I see...

Well I prefer your idea since it's just cool! One word does beat one line! Haha.

Thanks again.