views:

54

answers:

5

Hello, i have an object - Employee, and i want to know how to insert this object to a map structure sorted by char* lastName field. Thanx. My map need to contain pointers to Employee objects not the objects themselves. the key is the last name of the employee, the map need to be sorted by the employees last name, should i use multimap?

+2  A: 

So you've got an std::map with a custom comparator function (you've overloaded the less than operator) and you want to insert objects so that they're in the right order?

myMap.insert( make_pair( myKey, myEmployee ) );

where myKey is the key to your map. However, from the sound of your question it actually sounds like you're using the object as it's own key. In that case, just use std::set and

mySet.insert( myEmployee );

I'd also suggest that you not use char* as a means to store lastName and prefer the std::string.

EDIT:

Following the comments down below, are you having to be able to pass in a const char* into another function? If so, still use string as it has a nice method .c_str() specifically for legacy compatibility. It can be used as so:

void stuffHappens(const char* _input){
    //magic happens in here
}

stuffHappens( myString.c_str() );

and voila, you're much safer!

wheaties
Thanx for the quick reply, the first one was the one i need, the key is the last name of the employee. sure i would have used the string, unfortunately it's not allowed in my work.
Roy Gavrielov
@Roy, you should have mentioned this is a homework question. Please tag the question as `homework`.
wilhelmtell
Ok. i'll mention.
Roy Gavrielov
@Roy also consider replacing your professor with a better one. There is no excuse for enforcing use of `char*` instead of `std::string` in a C++ class.
wilhelmtell
@wilhelmtell i wish i could replace him..
Roy Gavrielov
+1  A: 

Define bool operator <(const Employee& other) for the Employee class. Alternatively, define bool operator <(const Employee& left, const Employee& right) as a non-member function, and make it a friend of Employee. The advantage of the first approach is that it's local to Employee, and that enhances encapsulation. The advantage of the latter approach is that it will work with any two types that are convertible to Employee. Alternatively, you can create a comparator functor, instead of operator <(), and pass that the map's constructor.

Lastly, use the insert() member function of std::map<> to insert the new employees. std::map<> also has operator []() defined, and it allows you to insert into the map. The insert() function will not insert the element if the element is already in the map, whereas operator []() will update the element if it already exists.

wilhelmtell
If he's using a map that's keyed on a char * then overloading < or making a functor isn't really necessary. If he switches to a set (which it sounds like he might want to) then this makes sense.
Niki Yoshiuchi
@Niki but his map is not keyed on `char*`. It's keyed on `Employee`.
wilhelmtell
@wilhelmtell - his question is a little ambiguous. It sounds like he wants a map of Employees keyed on their last name, not a map of something else keyed on Employees (if that is the case you are absolutely right). Most likely he should switch to a `set` in which case you are also completely correct.
Niki Yoshiuchi
"It sounds like he wants a map of Employees keyed on their last name" that's right.
Roy Gavrielov
@Roy Ah. In that case you want to use a `std::set<>`, because otherwise it's an utter waste of memory (if each string is duplicated) or violation of information hiding if the keys point at the strings of the objects.
wilhelmtell
@wilhelmtell i'm not duplicating the objects, i'm duplicating pointers, i need to hold pointers to the objects from 2 different places.
Roy Gavrielov
@Roy if your keys point at the member `lastName` of `Employee` objects then you are violating information hiding. What this means is that the `Employee` objects are now not allowed to reallocate their `lastName` member (for example, when a string grows), because there are external pointers pointing at them. Reallocating a `lastName` will invalidate one of those pointers, which means your program is in an invalid state. Dereferencing an invalid pointer will break all Hell loose (i.e., Standard C++ allows, at this point, your program to run as normal, to crash, or for aliens to abduct you).
wilhelmtell
You could always do myMap.insert(make_pair(myEmployee.lastName, myEmployee)) but that's a bad idea. You lose a lot of guarantees that map provides and you violate encapsulation. You're better off using a `set`.
Niki Yoshiuchi
@Niki if i use set can i deal with 3 similiar last names?
Roy Gavrielov
@Roy a set will only store distinct values. What "distinct" means is up to you: you might define two strings as distinct if they are not identical, or maybe if they are not "similar".
wilhelmtell
@Roy - As wilhelmtell said, no. The same is true for maps, however. What you want is a `std::multiset`. This makes some things more complicated however. Instead of using `find` (which will return an iterator to a single element) you should use `equal_range` which will give you a pair of iterators - one to the first element matching what you searched for and on to the element *after* the last element matching what you searched for.Alternatively you can use `lower_bound` and `upper_bound`. http://www.cplusplus.com has a decent reference for this kind of stuff.
Niki Yoshiuchi
+1  A: 

You could create a "functor" which overrides operator(), taking references to two of your objects and returning a boolean of obj1 < obj2.

For example:

class EmployeeComparator
{
    public:
    bool operator()(const Employee& emp1, const Employee& emp2)
    {
        return strcmp(emp1.lastName, emp2.lastName) < 0;
    }
}

When you create your std::map, you then pass EmployeeComparator as the comparison object, like so:

std::map<Employee, T, EmployeeComparator> m;

Other posters' ideas of overriding the < operator will work too, but with that method you have pick a single criteria to sort by. With my way, you could have one map that sorts by last name, another that sorts by employee ID, etc. With each map you'd pass a different functor as your sorter.

Keep in mind that with my way, if you are trying to sort by a private field, declare your sorting functor as a friend class of the class to be sorted.

Ken Simon
+1  A: 

To add to what wheaties said, if the Employee object contains its own key, you should probably use a set. In order to use the set, you are going to have to explicitly define a method of comparison for the Employee object. There are two ways of doing that, either define operator< for the object:

class Employee
{
public:
  bool operator<(const Employee &rhs)
  {
    return strcmp(lastName, rhs.lastName) < 0;
  }
...
};

Or you can define a functor that you tell the set to use for comparison:

struct EmployeeLessThan
{
  bool operator()(const Employee &lhs, const Employee &rhs)
  {
    return strcmp(lhs.lastName, rhs.lastName) < 0;
  }
};

std::set<Employee, EmployeeLessThan> myEmployees;

Edit: One VERY important thing to keep in mind is that a set stores all of it's items as const. It does this because it's possible that a change to an element in the set could change the order of the elements and set isn't capable of detecting this and reordering. This can be problematic if you want to update any of the employees contained in the set. The best way around this problem is to declare any of the data members of Employee that you might want to change as mutable. This will let you change their values even when it's const.

Edit2: If you decide to continue using a map and the key is a char *, then keep in mind that by default the map is going to compare two char *s by the value of their pointer, not the string they point to. The best thing to do in this case is to use std::string instead of char *. Alternatively, you can define a functor to pass to the map:

struct CharStarLessThan
{
  bool operator()(const char *lhs, const char *rhs)
  {
    return strcmp(lhs, rhs) < 0;
  }
};

std::map<char *, Employee, CharStarLessThan> myEmployees;
Niki Yoshiuchi
A: 

As Philip Potter has written in comments, you should definetely use std::string. Because two different char* declared at several places/files in your cpp code WON'T have the same addresses ! As a char* is nothing but an integer, you will have several char* keys for one and only one same lastname, and this is not what you want.

go this way:

std::map<std::string,YourType> yourMap;
std::string strMyLastName = "Rolland";
YourType aValue;
yourMap[strMyLastName ] = aValue;
Stephane Rolland
@Stephan i can't use string.
Roy Gavrielov
@Roy, and please why couldnt you use std::string ? ? ? I am really serious when I tell you that you will have dupplicate key entree for the same lastnames. And if you write C++, use std::strings and not char* as most as you can, and unless forced to do the contrary.
Stephane Rolland
@Stephan i'm more pissed off than you about this, it's my stupid teach assistant who force us to do this. " lastName must be char*, all the other fields can be string"
Roy Gavrielov
:-))) so your teacher wants you to experiment with dupplicate keys ;-)
Stephane Rolland