tags:

views:

171

answers:

5

Below the map 'widgets' is always size of 1 for some reason. There should be 4 when it's done.

Output:

Widget: widget_ram_label:layout_bar:0 1
Widget: widget_ram_active:layout_bar:0 1
Widget: widget_ram_total:layout_bar:0 1
Widget: widget_wlan0_label:layout_bar:0 1

Here's widgets:

std::map<const char *, Widget *> widgets;

And here's the code:

void Generic::BuildLayouts() {
   for(std::map<const char*, std::vector<widget_template> >::iterator l = 
       widget_templates.begin(); l != widget_templates.end(); l++) {
       std::vector<widget_template> layout = l->second;
       for(unsigned int i = 0; i < layout.size(); i++ ) {
           Json::Value *widget_v = CFG_Fetch_Raw(root, layout[i].key);
           if(!widget_v) {
               error("No widget named <%s>", layout[i].key);
               continue;
           }
           Json::Value *type = CFG_Fetch_Raw(widget_v, "type");
           if(!type) {
               error("Widget <%s> has no type!", layout[i].key);
               delete widget_v;
               continue;
           }

           Widget *widget;

           char name[256];
           sprintf(name, "%s:%s", layout[i].key, l->first);
           char tmp[256];
           int i = 0;
           sprintf(tmp, "%s:%d", name, i);

           while(widgets.find(tmp) != widgets.end()) {
               i++;
               sprintf(tmp, "%s:%d", name, i);
           }
           memcpy(name, tmp, 256);

           if(strcmp(type->asCString(), "text") == 0) {
               widget = (Widget *) new WidgetText(this, name, widget_v, 
                   layout[i].row, layout[i].col);
               std::cout << "Widget: " << name << " " << widgets.size() << std::endl;
           } else {
               error("Unknown widget type: %s", type->asCString());
           }
           if(widget) {
               widgets[name] = widget;
           }
           delete type;

       }
   }
}
+2  A: 

Maybe because all your name pointers poitns to the same buffer? Because the content of name changes, but not the value of the pointer to name in the map.

Try to use std::string instead.

Replace you name buffer by

#include <string >
//...
std::string name = "the name";

and replace your map by

std::map< const std::string , Widget* > widgets;

That will make your life easier, safer and more readable.

To help formatting, use std::stringstream or boost string algorithms.


A start of example, this code :

                            char name[256];
   sprintf(name, "%s:%s", layout[i].key, l->first);
   char tmp[256];
   int i = 0;
   sprintf(tmp, "%s:%d", name, i);

   while(widgets.find(tmp) != widgets.end()) {
    i++;
    sprintf(tmp, "%s:%d", name, i);
   }
   memcpy(name, tmp, 256);

would be written like this:

Widget *widget = NULL; // always initialize your variables!!!

std::stringstream name_stream; // this will let us format our string
       name_stream << layout[i].key << ":" << l->first;

std::string name = name_stream.str(); // now we got the string formated.

std::stringstream tmp_stream; // same for tmp
tmp_stream << name << ":" << i; // will automatically convert basic types, see the doc if you want specific formatting

std::string tmp = tmp_stream.str(); // now we got the string formated.

// the while loop have no sense : a map have only one value by key
// if you want to have several values by key, use std::multi_map instead -- it don't work exactly the same though
// for now i'll just assume you just need to find the value associated to the name:


typedef std::map< const std::string, Widget* > WidgetMap; // for ease of writing, make a shortcut! ease your life!
WidgetMap::iterator tmp_it = widgets.find( tmp );

if( tmp_it != widgets.end() ) 
{   
// starting here I don't understand you code, so I'll let you find the rest :)
}
Klaim
Then why is name different on each line of the output?
Scott
@Scott: Because the `memcpy` call changes the contents of the buffer. The value of `name` is still just an address.
eduffy
How do you do format strings with std::string? I could never figure it out, so I stuck with char pointers.
Scott
Because the content of name changes, but not the value of the pointer to name in the map.
Klaim
Will add an example...
Klaim
@eduffy: but this is a new 'name' each loop. I don't see how people are saying it's the same pointer.
Scott
@Scott: You can use `std::stringstream` as a replacement for `sprintf` .. it works like `cout`, but save the result in a `std::string`.
eduffy
@eduffy: Ok. I'll give that a try.
Scott
@Scott: Add the line `printf ("name = %p\n", name)` to see that the pointer never changes.
eduffy
...hmm hard to make an example starting with this obfuscated code...
Klaim
It's alright Klaim. I know how to use stringstream.
Scott
Ok, but i'll just add a start of example...
Klaim
It might work in practice, But still using `const std::string` as the Key type in `std::map` is illegal. Map requires its key type to be Assignable, while `const std::string` is not. The proper type for the Key in this case is just `std::string`.
AndreyT
Ah yes, you're right, will fix.
Klaim
@Scott: You are right. Formally, it is in fact a "new" buffer on each iteration. However, in practice it is reasonable to expect that on each iteration it will occupy exactly the same address as on the previous iteration. What you seem to miss is that the buffer *dies* (together with its content) at the end of each iteration. And on the next iteration the new buffer is allocated in the same place.
AndreyT
@AndreyT: I'm not sure where you got that requirement, but `std::map<Key, Value>::value_type` is typedef'ed to be `std::pair<const Key, Value>`, so it really isn't required to be assignable.
Greg Rogers
@Greg Rogers: The requirement is explicitly stated in Associative Container Requirements in the C++ standard (see 23.1.2/7). The new version also has it. And I never said that `syd::map<>::value_type` is supposed to be Assignable. I just said that Key type is required to be Assignable. I don't know though when its "assignability" is really used.
AndreyT
A: 

It's probably hashing on the address of name. Use std:string.

eduffy
+1  A: 

As an alternative to using std::string, you can just strdup the buffer before using it. Just remember to free it when you free your std::map.

Blindy
Also remember to free it if the key already existed and no insertion occurred (man this makes your life hard).
UncleBens
+2  A: 
std::map<const char *, Widget *> widgets;

Don't do this. Never use char* as a map key. (The map uses the std comparison std::less for its keys and that compares the addresses for pointers.)

Do yourself a favour and use std::string. Once you mastered this, you might try to go back dealing with C strings again.

sbi
+1  A: 

Your map doesn't know how to compare C-style strings properly. If you insist on using C-style strings as keys for your map, you need to supply the proper comparator. For example, it could be done as follows

inline bool str_less(const char *s1, const char *s2) {
  return strcmp(s1, s2) < 0;
}

...
std::map<const char *, Widget *, bool (*)(const char *, const char *)> widgets(str_less);

Or, if you wish, you can implement the comparator as a class-based functor.

But that's not all. Yor map does not know how to and will not manage memory for your 'const char *' keys. You are passing a pointer to a local buffer to the map as a key. This is totaly useless. In case of 'const char *' keys you are supposed to manage the key memory by yourself. For example, you can allocate each key dynamically and pass a pointer to such a dynamically allocated and properly initialized key buffer to the map. And later, when the time comes to destroy your map, it is your responsiblility to deallocate all these key buffers manually.

This all is a major pain in the neck. For this reason you should be much better off using 'std::string' as the key type, not a C-style string.

AndreyT