I wish to use c-strings instead of std::string for a performance situation. I have the following code:
std::map<const char*, int> myMap;
.
.
.
myMap.insert(std::pair<const char*, int>(str.c_str(), myint));
std::cout << myMap.count(str.c_str()) << std::endl;
Strangely enough the value I just entered returns 0 for count()?
By default, std::map uses std::less to compare the keys (which is the same as <, really, except it's guaranteed to work on unrelated pointers too). Which means it just does pointer comparison, definitely not what you want.
Just use the C++11 string type (std::string) instead of a legacy type used for nul-terminated strings (const char*) and you'll be fine.
Why do you think using raw C strings will increase performance?
Anyway, std::map has no special treatment for char pointers. It treats them like any other kind of pointer and not like strings, which means that it simply compares the keys with std::less. Perhaps confusingly, this is different from the behaviour of C++ streams, which do behave in a special way when passed a char const *.
You'd get the same behaviour with something like std::map<double *, int>, std::map<long *, int> or std::map<MyClass *, int>. It's interesting to note that the pointer comparison works because std::less is guaranteed to work with pointers, even though pointer comparison with < is formally unspecified behaviour.
So, you are obviously not interested in comparing the pointer values directly. If you want lexicographical string comparison, you can specify the comparison for your map via the third template parameter:
std::map<char const *, int, RawPointerComparion>
What I called RawPointerComparison in this example must be a functor taking two pointers and returning whether the first is less than the second. You can use the strcmp C function for that. This should do the trick:
struct RawPointerComparison
{
bool operator()(char const *lhs, char const *rhs) const
{
return strcmp(lhs, rhs) < 0;
}
};
It seems that you use variable str to enter different strings in the map. For example
str = "first";
myMap.insert( { str.c_str(), 1 } );
str = "second";
myMap.insert( { str.c_str(), 2 } );
str = "first";
std::cout << myMap.count(str.c_str()) << std::endl;
In this case the first str.c_str() is not equal to the last str.c_str() (where you compare pointers to allocated strings) because different memory regions were allocated in these cases.
If you would do the following
str = "first";
myMap.insert( { str.c_str(), 1 } );
std::cout << myMap.count(str.c_str()) << std::endl;
without intermediate statements then the result would be the output 1.
It seems that you are doing what you do not want.:)
Related
I have below code line:
const char *values[] = { "I", "We", "You", "We"};
std::set<const char*> setValues;
for( int i = 0; i < 3; i++ ) {
const char *val = values[i];
std::set<const char*>::iterator it = setValues.find( val );
if( it == setValues.end() ) {
setValues.insert( val );
}
else {
cout << "Existing value" << endl;
}
}
With this I am trying to insert non-repeated values in a set, but somehow code is not hitting to print for existing element and duplicate value is getting inserted.
What is wrong here?
The std::set<T>::find uses a default operator < of the type T.
Your type is const char*. This is a pointer to an address in memory so the find method just compares address in memory of given string to addresses in memory of all strings from set. These addresses are different for each string (unless compiler optimizes it out).
You need to tell std::set how to compare strings correctly. I can see that AnatolyS already wrote how to do it in his answer.
You should define less predicate for const char* and pass into the set template to make the set object works correctly with pointers:
struct cstrless {
bool operator()(const char* a, const char* b) const {
return strcmp(a, b) < 0;
}
};
std::set<const char*, cstrless> setValues;
Unless you use a custom comparison function object, std::set uses operator<(const key_type&,key_type&) by default. Two pointers are equal if, and only if they point to the same object.
Here is an example of three objects:
char a[] = "apple";
char b[] = "apple";
const char (&c)[6] = "apple"
First two are arrays, the third is an lvalue reference that is bound to a string literal object that is also an array. Being separate objects, their address is of course also different. So, if you were to write:
setValues.insert(a)
bool is_in_map = setValues.find("apple") != setValues.end();
The value of is_in_map would be false, because the set contains only the address of the string in a, and not the address of the string in the literal - even though the content of the strings are same.
Solution: Don't use operator< to compare pointers to c strings. Use std::strcmp instead. With std::set, this means using a custom comparison object. However, you aren't done with caveats yet. You must still make sure that the strings stay in memory as long as they are pointed to by the keys in the set. For example, this would be a mistake:
char a[] = "apple";
setValues.insert(a);
return setValues; // oops, we returned setValues outside of the scope
// but it contains a pointer to the string that
// is no longer valid outside of this scope
Solution: Take care of scope, or just use std::string.
(This answer plagiarises my own answer about std::map here)
Mapping of string to int is working fine.
std::map<std::string, int> // working
But I want to map C-style string to int
For example:
char A[10] = "apple";
map<char*,int> mapp;
mapp[A] = 10;
But when I try to access the value mapped to "apple" I am getting a garbage value instead of 10. Why it doesn't behave the same as std::string?
map<char*,int> mapp;
They key type here is not "c string". At least not, if we define c string to be "an array of characters, with null terminator". The key type, which is char*, is a pointer to a character object. The distinction is important. You aren't storing strings in the map. You are storing pointers, and the strings live elsewhere.
Unless you use a custom comparison function object, std::map uses operator<(const key_type&,key_type&) by default. Two pointers are equal if, and only if they point to the same object.
Here is an example of three objects:
char A[] = "apple";
char B[] = "apple";
const char (&C)[6] = "apple"
First two are arrays, the third is an lvalue reference that is bound to a string literal object that is also an array. Being separate objects, their address is of course also different. So, if you were to write:
mapp[A] = 10;
std::cout << mapp[B];
std::cout << mapp[C];
The output would be 0 for each, because you hadn't initialized mapp[B] nor mapp[C], so they will be value initialized by operator[]. The key values are different, even though each array contains the same characters.
Solution: Don't use operator< to compare pointers to c strings. Use std::strcmp instead. With std::map, this means using a custom comparison object. However, you aren't done with caveats yet. You must still make sure that the strings must stay in memory as long as they are pointed to by the keys in the map. For example, this would be a mistake:
char A[] = "apple";
mapp[A] = 10;
return mapp; // oops, we returned mapp outside of the scope
// but it contains a pointer to the string that
// is no longer valid outside of this scope
Solution: Take care of scope, or just use std::string.
It can be done but you need a smarter version of string:
struct CString {
CString(const char *str) {
strcpy(string, str);
}
CString(const CString ©); // Copy constructor will be needed.
char string[50]; // Or char * if you want to go that way, but you will need
// to be careful about memory so you can already see hardships ahead.
bool operator<(const CString &rhs) {
return strcmp(string, rhs.string) < 0;
}
}
map<CString,int> mapp;
mapp["someString"] = 5;
But as you can likely see, this is a huge hassle. There are probably some things that i have missed or overlooked as well.
You could also use a comparison function:
struct cmpStr{
bool operator()(const char *a, const char *b) const {
return strcmp(a, b) < 0;
}
};
map<char *,int> mapp;
char A[5] = "A";
mapp[A] = 5;
But there is a lot of external memory management, what happens if As memory goes but the map remains, UB. This is still a nightmare.
Just use a std::string.
c_str() returning a const char*, I would assume the following code to print "equal", but it doesn't.
Could someone explain to me where am I wrong ?
string xx = "hello";
const char* same = "hello";
const char* buf = xx.c_str();
if (buf == same)
{
cout << "equal" << endl;
}
I would assume the following code to print "equal"
That's a wrong assumption. std::string copies the literal data to its internal buffer, so the pointers will differ.
In case you wanted to compare the data instead of pointers, don't use c_str() at all and compare the const char* to the string directly - the overloaded comparison operator will do what you expect it to.
operator== for const char* does not do a string comparison, it directly compares the pointers. The buffer used by xx is not the same as the same pointer, so they are not equal.
To do string comparison with a std::string and a const char* you can just use the operator provided by std::string:
if (xx == same)
{
cout << "equal" << endl;
}
Operator == for char* pointers compares the pointers themselves, not the strings they point to.
If you want to second-guess std::string, you need to use comparison routines, like following:
if (strncmp(buf, same, xx.size())
...
buf == same is not comparing the strings but the address of the pointers. Since they are two different variables they will have two different address that will not be equal. If you need the compare char*s or const char*s then you can use std::strcmp
You are comparing the values of two pointers, which can never be equal. same points to read-only memory, and the pointer returned by c_str is the current data buffer of the std::string object with a null terminator guaranteed to be appended.
You could write xx == same: this is because std::string overloads == with the appropriate data types. Alternatively, and perhaps less elegantly, you could use std::strcmp on the two pointers.
I have a map declared like this:
std::map<const char*, const char*> map2D;
The map is filled by the output from an API function, which returns const char*:
map2D.insert(std::pair<const char*, const char*>(TempA, TempB));
Now there are 50 TempA values and 50 TempB values, and there is one key with the name of "months". When I am searching for this key, I am getting "not found". E.g.:
std::map<const char*, const char*>::iterator it;
it = map2D.find("months");
if (it != map2D.end())
{
std::cout << "Found " << it->first << " " << it->second << '\n';
}
else
{
std::cout << "Not found\n";
}
But when I am doing it like this:
map2D.insert(std::pair<const char*, const char*>("months", "June");
I can find the respective month. After searching the web, I understand that this problem may be related to the use of const char*. Can anyone further clarify this?
Comparing two const char* for equality does not do what you think it does; it compares pointer values, not the strings that the pointers point to. With string literals this may occasionally "work", but you have no way of knowing that two string literals even with the same characters in it will be stored at the same address. You would have to provide a custom comparator that invokes strcmp, in order to make that work reliably.
You're much better off with a std::map<std::string, std::string>. It doesn't matter that your third-party API gives you const char*: you can simply construct std::strings from those.
This container will have elements with clear ownership and lifetime semantics, and be automatically ordered properly. In short, all your problems will simply go away.
If you still really need to store const char*, be aware that such a requirement is exceedingly rare and should only be even fleetingly considered if you are prepared to litter your code with explanatory comments detailing precisely how and why your container is safe; hint: it almost certainly is not.
The built-in comparison operator for const char* compares the pointer address, not the strings it points to:
std::map<const char*, const char*> map2D; //don't use that!
map2D.emplace("a", "b");
//...
auto key = std::string{"a"};
assert(map2D.find(key.c_str()) == map2D.end()); //not found
If you create a map from string literals (which you can assume the lifetime of a program) use C++17 std::string_view instead of const char* as the key. This class has custom operator= which does the job in a way you'd expect when using algorithms:
std::map<std::string_view, const char*> map2D;
map2D.emplace("a", "b");
//...
auto key = std::string{"a"};
assert(map2D.find(key)->second == std::string_view{"b"});
On the other hand, if you can't say nothing about the lifetime of the string pointed by const char*, use std::string which will make a deep copy. It is less-performant but more general solution:
std::map<std::string, const char*> map2D;
map2D.emplace("a", "b");
//...
auto key = std::string{"a"};
assert(map2D.find(key)->second == std::string_view{"b"});
From what I have deduced, the std::map::find() method searches the map by comparising pointer address instead of values. Example:
std::string aa = "asd";
const char* a = aa.c_str();
const char* b = "asd";
// m_options is a std::map<const char*, int )
m_options.insert( std::make_pair( a, 0 ) );
if( m_options.find( b ) != m_options.end() ) {
// won't reach this place
}
I am kinda surprised (because I am using primitive types instead of some class) and I think that I have done something wrong, if not then how to force it to use value instead of address?
You are using char * as a key type for the map. For the pointer types, comparison is performed by their address (as the map cannot know that these pointers are NULL-terminated 8-bit strings).
To achieve your goal, you could create the map with custom compare function, e.g.:
bool MyStringCompare(const char *s1, const char *s2) {
return strcmp(s1, s2) < 0;
}
...
std::map<const char*, int, MyStringCompare> m_options;
Or consider using std::string as the key type.
Actually, map uses a strict ordering comparison operator to look for values, not the equality operator. Anyway, you can achieve this by passing a custom functor that compares the values of the strings, or do the right thing and use std::string instead.