Sign in to follow this  
gretty

Storing a pointer to an objects member variable

Recommended Posts

Hello

I have a problem that has been annoying me for a while & I have yet to come up with a solution. I would like to create a container class that stores objects & sorts them according to ONE of their member variable values. So if I have a class of Students (with the member variable int studentID), I want to store them in my Container class according to their studentID value in ascending order. I have made the container class a templated class so I can use for any class in any of my projects.

[b]My problem with my Container class:[/b] I cannot store pointers of objects(for example Student objects). The problem more precisely is that I cannot store a reference(?) to an objects member variable (for example a reference/pointer to a Student's studentID variable).

This problem has been annoying me for ages & any information or advice would be highly appreciated. Is there a way to make my below Container class store pointers to objects member variables? Is there a different way to create a container of objects that can be sorted by their member variables?

[code]
#include <iostream>

using namespace std;

template <typename Object, typename dataType>
class Collection
{
public:

Collection( dataType Object::*nMemberVariable )
{
memberVariable = nMemberVariable;
}

bool store( Object* o )
{
// check that o is not a NULL pointer & not already present in maps
if ( o==NULL || instanceVarMap.find(o->*memberVariable) != instanceVarMap.end() )
{
return false;
}

instanceVarMap.insert( o->*memberVariable, o );
return true;
}

private:
dataType Object::* memberVariable;
std::map <dataType, Object*> instanceVarMap;
};


struct FoodItem
{
unsigned int ID;
string name;
double price;
};


int main()
{

// I am attempting to store a pointer to an objects member variable
// this is so I can create a custom container class(like a map or vector) that
// sorts its contents (which are FoodItem objects) according to their member variable values
// so a container could sort all its elements according to a FoodItems ID value or name value

Collection <FoodItem*> foodCol( &FoodItem::name );

string nNames[] = {"a", "b", "c", "d"};
double nPrices[] = {1.1, 2.2, 3.3, 4.4};

for (int i=0; i<4; i++)
{
FoodItem *f = new FoodItem() { i, nNames[i], nPrices[i] };
foodCol.store( f );
}

// Note storing an ACTUAL object is possible with this class
Collection

system("PAUSE");
return 0;
}
[/code]

Share this post


Link to post
Share on other sites
C++ does have member variable pointers, but they're not used that often, and in any case they're not how you'd typically solve a problem of this sort.

I only glanced over the code, but I'll go ahead and mention that the typical solution is to use a sorting function such as [url="http://msdn.microsoft.com/en-us/library/ecdecxh1(v=vs.80).aspx"]std::sort()[/url], and either implement the < operator for the type in question, or implement a custom comparator. (You'll want to use the latter method if the objects are stored by pointer or smart pointer; also, using a custom comparator is arguably more clear, since overloading the < operator for objects that wouldn't normally be 'less-than comparable' can be a little confusing.)

Share this post


Link to post
Share on other sites
What if I store the member variable reference like this

dataType Object::** memberVariable;

[code]
#include <iostream>
#include <map>
#include <string>

using namespace std;

template <typename Object, typename dataType>
class Collection
{
public:

Collection( dataType Object::*nMemberVariable )
{
memberVariable = nMemberVariable;
}

bool store( const Object& o )
{
// check that o is not a NULL pointer & not already present in maps
if ( o==NULL || instanceVarMap.find(o.*memberVariable) != instanceVarMap.end() )
{
return false;
}

instanceVarMap.insert( o.*memberVariable, o );
return true;
}

private:
dataType Object::* memberVariable;
std::map <dataType, Object*> instanceVarMap;
};


struct FoodItem
{
unsigned int ID;
string name;
double price;
};


int main()
{

// I am attempting to store a pointer to an objects member variable
// this is so I can create a custom container class(like a map or vector) that
// sorts its contents (which are FoodItem objects) according to their member variable values
// so a container could sort all its elements according to a FoodItems ID value or name value

Collection <FoodItem*, string> foodCol( &FoodItem::name );

string nNames[] = {"a", "b", "c", "d"};
double nPrices[] = {1.1, 2.2, 3.3, 4.4};

for (int i=0; i<4; i++)
{
FoodItem *f = new FoodItem() { i, nNames[i], nPrices[i] };
//FoodItem f = {i, nNames[i], nPrices[i] };
foodCol.store( &f );
}

system("PAUSE");
return 0;
}
[/code]

Share this post


Link to post
Share on other sites
Not sure if i understood but you could use inheritance and base all objects on a base class with that member? Like make the container take in a pointer to an object of type ContainsMyWeirdSortingVariable

Share this post


Link to post
Share on other sites
[quote name='gretty' timestamp='1307150339' post='4819277']
Hello

I have a problem that has been annoying me for a while & I have yet to come up with a solution. I would like to create a container class that stores objects & sorts them according to ONE of their member variable values. So if I have a class of Students (with the member variable int studentID), I want to store them in my Container class according to their studentID value in ascending order.
[/quote]
With a little coaxing, std::set will do this:

[code]
#include <set>
#include <functional>
#include <string>
#include <iostream>

// This can be reused for any member variable type
template<typename T, typename MemberType>
struct memvar_compare : public std::binary_function<T, T, bool>
{
const MemberType T::*memvar_ptr;
memvar_compare(const MemberType T::* memvar_ptr) : memvar_ptr(memvar_ptr) { }
bool operator() (const T &lhs, const T &rhs) const
{
return (lhs.*memvar_ptr) < (rhs.*memvar_ptr);
}
};

struct person
{
person(const std::string &name, unsigned age) : name(name), age(age) { }

std::string name;
unsigned age;
};

int main()
{
typedef memvar_compare<person, std::string> person_compare;
std::set<person, person_compare> people(&person::name);

//typedef memvar_compare<person, unsigned> person_compare;
//std::set<person, person_compare> people(&person::age);

people.insert(person("Mozart", 35));
people.insert(person("Gandhi", 78));
people.insert(person("Xerces I", 54));
people.insert(person("Me", 29)); // delusions of grandure :)

for (std::set<person, person_compare>::const_iterator it = people.begin(), e = people.end(); it != e; ++it)
std::cout << it->name << ", age " << it->age << '\n';

return 0;
}
[/code]

Share this post


Link to post
Share on other sites
Just to reiterate jyk's advice, as crowbarring the use of a particular container in order to get this behaviour seems a bit daft:

[source lang="c++"]
struct FoodItem
{
FoodItem(unsigned int ID,string name,double price) : ID(ID),name(name),price(price) { }

unsigned int ID;
string name;
double price;
};

bool Less(const FoodItem &a,const FoodItem &b)
{
return a.ID<b.ID;
}

void f()
{
std::vector<FoodItem> items; // or pretty much any other standard container you want

items.push_back(FoodItem(10,"chips",12.3));
items.push_back(FoodItem(5,"bread",14.3));

std::sort(items.begin(),items.end(),Less);
}
[/source]

While a std::map would actually insert the items in sorted order, as owl points out, the additional overhead of storing such small POD types in a map would almost certainly vastly outweigh this benefit.

Also, since when did this work? Have I missed something?

[source lang="c++"]
FoodItem *f = new FoodItem() { i, nNames[i], nPrices[i] };
[/source]

Share this post


Link to post
Share on other sites
It [url="http://www.gamedev.net/topic/602016-my-custom-container-class-wont-let-me-store-a-member-variable-pointer/page__p__4811014#entry4811014"]still[/url] sounds like you want a database. Have you considered such a solution?

What are your requirements if the value of the member changes over time?


[quote]
Also, since when did this work? Have I missed something?
[code]
FoodItem *f = new FoodItem() { i, nNames[i], nPrices[i] };
[/code]
[/quote]
Looks like a C++0x initialiser list.

Share this post


Link to post
Share on other sites
[quote name='rip-off' timestamp='1307204425' post='4819451']
It [url="http://www.gamedev.net/topic/602016-my-custom-container-class-wont-let-me-store-a-member-variable-pointer/page__p__4811014#entry4811014"]still[/url] sounds like you want a database. Have you considered such a solution?

What are your requirements if the value of the member changes over time?


[quote]
Also, since when did this work? Have I missed something?
[code]
FoodItem *f = new FoodItem() { i, nNames[i], nPrices[i] };
[/code]
[/quote]
Looks like a C++0x initialiser list.
[/quote]

Thanks for everyones replies.

Yes a database would be best but I am using Mosync(Cross Platform Mobile API) & it has limitations. These limitations being, no SQL support (I can write it myself but I dont have time, it does have XML support tho), no STL support (it has its own Set, Queue, Stack, Vector & Maps tho), no threading & doesn't support C++ exceptions.

So to me the best alternative would be a templated container that can be arranged by an object's specific data member (I thought this was a pretty smart way to do this; write it once then use it in many apps). I will look into using the MAUtil::Set class for this tho.

Share this post


Link to post
Share on other sites
Ok, I have worked with the Mosync Collection objects & subclassed a Set but now I have completely confused myself. I think I should have actually subclassed/or just used a HashMap not a Set but I was following the suggestions.

I would like to use the set like so:
[code]
struct FoodItem
{
int ID;
string name;
float price;
};

SortedContainer <FoodItem*, string> col;
// after adding items to the collection I get items easily by the [] operator

FoodItem* coffee = col["coffee"];
FoodItem* coke = col["coke"];
[/code]

From looking at the below code, do you suggest using a MAUtil::HashMap [url="http://www.mosync.com/files/imports/doxygen/latest/html/class_m_a_util_1_1_hash_map.html"]http://www.mosync.com/files/imports/doxygen/latest/html/class_m_a_util_1_1_hash_map.html[/url] or continue with a MAUtil::Set [url="http://www.mosync.com/files/imports/doxygen/latest/html/class_m_a_util_1_1_set.html"]http://www.mosync.com/files/imports/doxygen/latest/html/class_m_a_util_1_1_set.html[/url]?
[code]
#ifndef SORTEDCONTAINER_H
#define SORTEDCONTAINER_H

#include <MAUtil/Set.h>

using namespace MAUtil;

template <typename Object, typename dataType>
class SortedContainer : public Set
{
public:
SortedContainer( dataType Object::*nMemberVariable )
: Set<Object>( compare )
{
memberVariable = nMemberVariable;
}

template <typename Object, typename dataType>
bool compare( const Object &a, const Object &b, )
{
return a.*memberVariable < b.*memberVariable;
}


private:
dataType Object::* memberVariable;

};

// & use it like this
SortedContainer <FoodItem*, MAUtil::String> foodCont( &FoodItem::name );


// Or should I just do this
template <typename Object, typename dataType>
bool compare( const Object &a, const Object &b, dataType Object::*memberVariable )
{
return a.*memberVariable < b.*memberVariable;
}

// & then I use it like this
Set <FoodItem*> foodSet( compare );


#endif // SORTEDCONTAINER_H

[/code]

Share this post


Link to post
Share on other sites
I think your first one is the only option. The second won't work as the signature of your compare function does not match that expected by the container. I can't tell from the Doxygenerated information whether this Set implementation defaults to operator< in the absence of a compare function, but as was said above, it can be confusing to implement operator< just for the sake of sorting.

It looks like, unlike the C++ standard library, these containers are okay to inherit from, since the base Dictionary class has a virtual destructor, so I'd say your first approach is perfectly valid, if a little unusual.

[EDIT] I just typed a load of nonsense about using a functor instead, but since the compare function is not a template in this Set implementation, that won't work.

Whether or not a hash-map would be a better option depends on the speed of lookup required and the quality of a possible hashing function in terms of how infrequently clashes of hashed values would occur, which is hard to predict if you are planning to sort by radically different types in different instantiations of your containers, and how the hashmap is implemented internally e.g. the substructure used to represent items who get hashed to the same value.

Share this post


Link to post
Share on other sites
[quote]
Yes a database would be best but I am using Mosync(Cross Platform Mobile API) & it has limitations.
[/quote]
Fair enough. You didn't answer my question about what is supposed to happen if the member variable's value changes? Do you allow this?

What about:
[code]
int compareId(const FoodItem *a, const FoodItem *b)
{
return a->id - b->id;
}

int compareName(const FoodItem *a, const FoodItem *b)
{
return a->name.compare(b->name);
}

class FoodStorage : noncopyable
{
public:
FooSdtorage()
:
byId(&compareId),
byName(&compareName)
{
}

~FoodStorage();

void store(const FoodItem &item)
{
FoodItem *ptr = new FoodItem(item);
byId.insert(ptr);
byName.insert(ptr);
}

FoodItem *getByName(const std::string &name);

FoodItem *getById(int id);

// ...

private:
Set<FoodItem *> byName;
Set<FoodItem *> byId;
};
[/code]
It seems to me that you are doing this the hard way at the moment, by trying to use member function pointers. Your compare functions are not the correct type for the MAUtils dictionary. You cannot add a third parameter and expect it to work. Also the function should return an int, not a boolean.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this