C++ map object not growing when members added - c++

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;
}
}
}

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.

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 :)
}

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. Your map does not know how to (and will not) manage memory for your const char * keys. You are passing a pointer to a local short-lived buffer to the map as a key. This is totally 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 responsibility 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.

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.

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

Related

How can I convert between enum and cstring without map?

I've seen this answered before but using map or vectors, but I can't use outside libraries for my project, so I need to figure out another way. For converting to a cstring I'm using a function with a switch case and it works, but for converting from a cstring to the enum isn't going as planned.
The method I came up with for converting a cstring to an enum is to first cast the enum to an int (none, first, second, etc.. becomes 0, 1, 2, etc...) so that I can use a for loop to iterate through the different enums. Next, using the enum to cstring function, I compare the string passed in to the string given by the converting function. If they are equal, the enum is set. This seems like a pretty convoluted way to do this, and unsurprisingly, I can't get it working.
Here's all my test code, the setType function is where things go wrong.
enum type { none, first, second, third, fourth };
const char* typeName(type name);
type setType(char* name); // trouble here
int myStrComp(const char *str1, const char *str2); // compare cstrings
int main() { // test the function
char testName[] = "second";
type testType = setType(testName);
std::cout << typeName(testType) << std::endl; // should print "second"
}
const char* typeName(type name) { // convert enum to cstring
switch (name) {
case none: return '\0'; break;
case first: return "first"; break;
case second: return "second"; break;
case third: return "third"; break;
case fourth: return "fourth"; break;
}
}
type setType(char* name) {
type temp;
for (int i = 0; i < 4; i++) { // I know, inefficient
temp = static_cast<type>(i); // but there's only 5 to check
if (myStrComp(name, typeName(temp)) == 0) {
return temp;
}
}
return none; // shouldn't get here
}
int myStrComp(const char *str1, const char *str2) {
while (*str1 == *str2) {
if (!*str1) {
return 0; // strings are equal
}
str1++;
str2++;
}
return *str1 - *str2; // how different are they alphabetically
}
case none: return '\0'; break;
This has single quotes, so it returns character \0, which as an integer is equal to 0. When converted to pointer, this is a null pointer. When you try to dereference a null pointer in myStrComp(), an access violation happens.
Instead, you can use return ""; to return an empty string.
Possible way to simplify typeName is to use an array:
const char* typeName[] = {"", "first", "second", "third", "fourth"};
if (myStrComp(name, typeName[i]) == 0)
(This will cause access violation if i is out of bounds.)
To answer your question, you can associate enums with text by using a lookup table:
struct Entry
{
type enum_type;
const char * enum_text;
};
Entry enum_conversion_table[] =
{
{none, "none"},
{first, "first"},
{second, "second"},
{third, "third"},
{fourth, "fourth"},
};
static const size_t conversion_table_capacity =
sizeof(conversion_table) / sizeof(conversion_table[0]);
Converting from enum to text:
Search the table for an entry with the key enum.
Return a pointer to the text field of the entry, if found, or nullptr if not found.
Converting from text to enum:
Search the table for an entry with the key enum text.
Return the enum value if found, or create another enum value for "unknown" and return that.
This technique:
1. Doesn't use any libraries.
2. The data can be placed into the constant data section and stored into read-only memory.
3. The code can access the data directly.
4. The data is initialized before main().

Cannot Convert 'int' to 'const char *'

I'm working with c++ and XE8. Given the following code:
String __fastcall RemoveCharsFromString(String &str, const String &c)
{
for(unsigned int i=0; i<c.Length(); ++i)
{
str.Delete(std::remove(str[0], str.LastChar(), c[i]), str.LastChar());
}
}
Received errors:
Using str.Delete(remove(str[0], str.LastChar(), c[i]), str.LastChar()); results in a
Cannot convert 'int' to 'const char *'
error inside the for loop.
Using str.Delete(std::remove(str[0], str.LastChar(), c[i]), str.LastChar()); results in a
Could not find a match for 'remove(wchar_t,wchar_t*,wchar_t)'
error inside the for loop.
Searching SO and the web, it's my understanding this error is usually received when code is written with single quotes when double quotes should have been used. I don't believe that scenario is applicable in this case.
The return type of String is Embarcadero's UnicodeString. Details can be found here: RAD Studio VCL Reference - UnicodeString Class
In C++Builder, String refers to System::String, which is an alias for System::UnicodeString in XE8.
There are a lot of mistakes in your code.
The System::String::Delete() method expects an index and a count as input, but that is not what you are trying to pass to it. You are expecting Delete() to work like the STL std::wstring::erase() method, and that is simply not the case.
You are not taking into account that System::String::operator[] is 1-based. It is not 0-based, like your code is assuming.
The System::String::LastChar() method returns a pointer to the last UTF-16 encoded Unicode character in the string. It does not return a pointer to the string's null terminator, like your code is assuming.
You are calling the STL std::remove() algorithm that takes a range of iterators as input, shifts all copies of the specified value to the end of the range, and then returns a new iterator to where the "removed" values have been moved to within the range (so they can be erase()'d from the container that owns the iterators). You cannot mix System::String::Delete() and std::remove() the way you are attempting to do. If you really want to use std::replace(), you need to use it more like this instead:
String __fastcall RemoveCharsFromString(String &str, const String &c)
{
for(int i = 1; i <= c.Length(); ++i)
{
const Char *start = str.c_str();
const Char* end = start + str.Length();
Char* ptr = std::replace(start, end, c[i]);
str.Delete(1 + std::distance(start, ptr), std::distance(ptr, end));
}
}
That being said, Embarcadero's RTL has its own System::Sysutils::StringReplace() function that you can use instead of std::replace():
#include <System.SysUtils.hpp>
String __fastcall RemoveCharsFromString(String &str, const String &c)
{
for(int i = 1; i <= c.Length(); ++i)
{
str = StringReplace(str, c[i], L"", TReplaceFlags() << rfReplaceAll);
}
}
Or, if you need to take UTF-16 surrogates into account in the c string (which std::remove() does not account for):
#include <System.SysUtils.hpp>
#include <System.Character.hpp>
String __fastcall RemoveCharsFromString(String &str, const String &c)
{
int i = 1;
while (i <= c.Length())
{
String chr;
if (IsSurrogatePair(c, i))
chr = c.SubString(i, 2);
else
chr = c.SubString(i, 1);
str = StringReplace(str, chr, L"", TReplaceFlags() << rfReplaceAll);
i += chr.Length();
}
}

What's the difference between char * and const_cast<char*>(string.c_str())

I use an external library to deal with udp (OSC) communication between 2 apps.
To format the messages that will be sent, the library is expecting a char* but I get a string from the UI that I have to convert.
While I was dealing with other parts of my code the udp part was hard coded like that :
char* endofMess = "from setEndMess";
and was working fine. I thought it would be easy to get it working with my strings and wrote :
std::string s = "from setEndMess";
char* endofMess = const_cast<char*>(s.c_str());
but unlike for the first example where I was receiving the message correctly formatted, I now receive only gibberish characters. Does somebody know where it can come from?
Thanks!
Matthieu
EDIT : the code I use :
The method to send the message each time OSCVal will change :
void osc500::testOSC(int identifier, float OSCval)
{
UdpTransmitSocket transmitSocket( IpEndpointName( destIP, port ) );
char buffer[1024];
osc::OutboundPacketStream p( buffer, 1024 );
p << osc::BeginBundleImmediate
<< osc::BeginMessage( endofMess )
<< OSCval << osc::EndMessage
<< osc::EndBundle;
transmitSocket.Send( p.Data(), p.Size() );
}
And if I have to change the OSC pattern I call this one :
void osc500::setEndMess(String endpattern){
// endofMess = "from setEndMess"; //OK works fine each time it's called
//1st try :
//std::string s = "from setEndMess";
//endofMess = const_cast<char*>(s.c_str()); //gibberish
//2nd try :
//std::string s = "from setEndMess";
//endofMess = &s[0]; //gibberish
//3rd & 4th tries :
//char s[4] = {'t','e','s','t'};
//char s[5] = {'t','e','s','t','\0'};
//endofMess = s; //gibberish
}
c_str() is for read-only access of std::string.
If you want to write to a string through pointers, then use either...
an array (or vector) of char instead of std::string
char* buf = &str[0]; - point directly to the first character of a string
Trick (2) is guaranteed to work under C++11; in practice it works in C++03 too but that relies on compiler implementation of std::string having contignuos storage.
(And whatever you do, keep an eye on the buffer length.)
I suspect the char* is not written to, it is only non const because it is a legacy API. If so, your problem is probably that the std::string has fallen out of scope or been modified between the point where you call c_str and where it is used in the guts of the API.
If you want to modify the std::string content, I think you should use &s[0] (making sure that the string is big enough).
std::string s = "abcdef...";
char* ptr = &s[0];
e.g. (tested with MSVC10):
#include <iostream>
#include <ostream>
#include <string>
using namespace std;
void f(char* ptr, size_t count)
{
for (size_t i = 0; i < count; i++)
ptr[i] = 'x';
}
int main()
{
string s = "abcdef";
cout << s << endl;
f(&s[0], 3);
cout << s << endl;
}
Output:
abcdef
xxxdef

c++ element of string array (chars) relies on source string

A little context: I'm trying to make a very simple hashing function/hash table as described here. I'm basically on the first step, blindly adding a key to an array based on the letter it starts with (no checking if space is occupied yet). The code I'm using to do this so far:
int main(int argc, char **argv) {
char *arrayKeys[300];
std::string aName("Charles");
char *aNameCpy = new char[aName.size() + 1];
std::copy(aName.begin(), aName.end(), aNameCpy);
aNameCpy[aName.size()] = '\0';
int kPos = storeKey(arrayKeys, aNameCpy);
std::cout << "The new position in arrayKeys for 'Charles' is: " <<
kPos << "\ncontaining the text: " << arrayKeys[kPos] << std::endl;
delete[] aNameCpy;
return 0;
}
int storeKey(char **keys, char *key) {
int charLett = -1;
charLett = (int)key[0];
if(charLett != -1)
charLett = charLett - 65;
keys[charLett * 10] = key;
return charLett*10;
}
My question is, how can I add a string to the array (arrayKeys) where it is fully apart of the array, and not reliant upon the original string? If I delete the copy of the string (aNamCpy) before I print the array key out, the array key turns into garbled symbols. I'm copying the string before sending it to the function because I need a non-const string to add to the arrayKeys array (so it can be modified), and any method of string I looked at seemed to return const.
(Another version of this I attempted can be found here, but I would rather not initialize the arrayKeys like that - with a definite second dimension (string) length)
C++ is still very new to me so I can't figure out how to juggle the non-const part with copying the string into arrayKeys. Any help would be very much appreciated.
Here's how I'd change the code to use more modern C++ constructs. I think you'll find this way easier to use.
int storeKey(vector<string> &keys, const string &key) {
int charLett = -1;
if (!key.empty()) { // you weren't doing this before!
charLett = key[0];
charLett = toupper(charLett) - 'A';
keys[charLett * 10] = key;
}
return charLett*10;
}
int main() {
vector<string> arrayKeys(300);
std::string aName("Charles");
// No need to bother with the awkward copying.
// std::vector and std::string will take care of it for us.
int kPos = storeKey(arrayKeys, aName);
if (kPos >= 0) {
cout << "The new position in arrayKeys for 'Charles' is: " <<
kPos << "\ncontaining the text: " << arrayKeys[kPos] << endl;
}
// Don't have to remember to delete anything because nothing was new'ed.
return 0;
}
(#Kristo has the right idea. I'm just going to add a comment to the question as asked.)
Basically, don't delete aNameCpy. You require the copy to remind valid and therefore it shouldn't be deleted. You should only delete the strings if and when you ever delete the entire hash.
C++ is still very new to me so I can't figure out how to juggle the
non-const part
You could declare both keys and key to be const char **keys, const char *key. keys is pointer-to-pointer-to-char. More precisely, it's a pointer to nonconst pointer to const char. In other words, you can modify keys, you just cannot modify the actual characters it points (indirectly) at.
So, simply put const in your declaration of storeKey int storeKey(const char **keys, const char *key) and update arrayKeys accordingly const char *arrayKeys[300];
One final style issue: You should copy the string inside storeKey, not in main. This is better design, as it makes clear to the reader that storeKey "owns" the copy.
int storeKey(char **keys, const char *key) {
char * the_copy = new char[strlen(key)+1];
strcpy(the_copy, key);
... and so on
But, in short, use C++ string instead of all this, if you can!

Can getline() be used to get a char array from a fstream

I want to add a new (fstream) function in a program that already uses char arrays to process strings.
The problem is that the below code yields strings, and the only way i can think of getting this to work would be to have an intermediary function that would copy the strings, char by char, into a new char array, pass these on to the functions in the program, get back the results and then copy the results char by char back into the string.
Surely (hopefully) there must be a better way?
Thanks!
void translateStream(ifstream &input, ostream& cout) {
string inputStr;
string translated;
getline(input, inputStr, ' ');
while (!input.eof()) {
translateWord(inputStr, translated);
cout << translated;
getline(input, inputStr, ' ');
}
cout << inputStr;
the translateWord func:
void translateWord(char orig[], char pig[]) {
bool dropCap = false;
int len = strlen(orig)-1;
int firstVowel = findFirstVowel(orig);
char tempStr[len];
strcpy(pig, orig);
if (isdigit(orig[0])) return;
//remember if dropped cap
if (isupper(orig[0])) dropCap = true;
if (firstVowel == -1) {
strcat(pig, "ay");
// return;
}
if (isVowel(orig[0], 0, len)) {
strcat(pig, "way");
// return;
} else {
splitString(pig,tempStr,firstVowel);
strcat(tempStr, pig);
strcat(tempStr, "ay");
strcpy(pig,tempStr);
}
if (dropCap) {
pig[0] = toupper(pig[0]);
}
}
You can pass a string as the first parameter to translateWord by making the first parameter a const char *. Then you call the function with inputStr.c_str() as the first parameter. Do deal with the second (output) parameter though, you need to either completely re-write translateWord to use std::string (the best solution, IMHO), or pass a suitably sized array of char as the second parameter.
Also, what you have posted is not actually C++ - for example:
char tempStr[len];
is not supported by C++ - it is an extension of g++, taken from C99.
You can use the member function ifstream::getline. It takes a char* buffer as the first parameter, and a size argument as the second.