I have the following code to test smart pointer as key for std::map
, I run the code on Mac and Linux, but I observed different output, is it a bug or have I done anything wrong?
#include <iostream>
#include <memory>
#include <string>
#include <map>
using namespace std;
class Dog {
public:
typedef shared_ptr<Dog> sptr;
Dog(const string &name) : name_(name) { }
friend bool operator<(const Dog &lhs, const Dog &rhs) {
cout << "Dog::operator< called" << endl;
return lhs.name_ < rhs.name_;
}
friend bool operator<(const sptr &lhs, const sptr &rhs) {
cout << "Dog::operator< sptr called" << endl;
return lhs->name_ < rhs->name_;
}
private:
string name_;
};
void test_raw_object_as_map_key() {
cout << "raw object as map key ============== " << endl;
map<Dog, int> m;
m[Dog("A")] = 1;
m[Dog("B")] = 2;
m[Dog("C")] = 3;
m[Dog("A")] = 4;
cout << "map size: " << m.size() << endl;
}
void test_smart_pointer_as_map_key() {
cout << "smart pointer as map key ============== " << endl;
map<Dog::sptr, int> m;
m[make_shared<Dog>("A")] = 1;
m[make_shared<Dog>("B")] = 2;
m[make_shared<Dog>("C")] = 3;
m[make_shared<Dog>("A")] = 4;
cout << "map size: " << m.size() << endl;
}
int main(int argc, const char *argv[]) {
test_raw_object_as_map_key();
test_smart_pointer_as_map_key();
return 0;
}
On Mac:
neevek@MAC$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.1.0
Thread model: posix
neevek@MAC$ ./a.out
raw object as map key ==============
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
map size: 3
smart pointer as map key ==============
Dog::operator< sptr called
Dog::operator< sptr called
Dog::operator< sptr called
Dog::operator< sptr called
Dog::operator< sptr called
Dog::operator< sptr called
Dog::operator< sptr called
Dog::operator< sptr called
Dog::operator< sptr called
map size: 3
On Linux:
neevek@LINUX$ g++ --version
g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
neevek@LINUX$ ./a.out
raw object as map key ==============
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
Dog::operator< called
map size: 3
smart pointer as map key ==============
map size: 4
Pointers can be used as keys but especially with a std::map (or std::set) I would not advise it. The behavior of the program is not deterministic i.e. when one iterates over the map the order in which the items in the map are iterated is not guaranteed to be the same.
Use when you want to assign one raw pointer to multiple owners, for example, when you return a copy of a pointer from a container but want to keep the original. The raw pointer is not deleted until all shared_ptr owners have gone out of scope or have otherwise given up ownership.
A Smart Pointer is a wrapper class over a pointer with an operator like * and -> overloaded. The objects of the smart pointer class look like normal pointers. But, unlike Normal Pointers it can deallocate and free destroyed object memory.
Smart pointers try to prevent memory leaks by making the resource deallocation automatic: when the pointer to an object (or the last in a series of pointers) is destroyed, for example because it goes out of scope, the pointed object is destroyed too. Save this answer.
GCC is right (on Mac as you see g++
is in fact clang): std::map
uses std::less<T>
to compare keys. That in turn calls operator <
on the arguments, but the lookup is performed first in namespace std
, so it finds the default implementation for shared_ptr
, comparing internal pointers. To make this work you have to specialize std::less
for shared_ptr<Dog>
:
namespace std {
template<>
struct less<shared_ptr<Dog>> {
bool operator() (const shared_ptr<Dog>& lhs, const shared_ptr<Dog>& rhs) {
return *lhs < *rhs;
}
};
}
The default Compare
object of std::map
is std::less<Key>
, which is std::shared_ptr<Dog>
in your case. So it looks for std::less< std::shared_ptr<Dog> >
implementation, and it just compares the pointer address.
To specify the Compare
object, you can just define it by yourself and use it in map
class MyCompare {
public:
bool operator() (const sptr& l, const sptr& r) {
return *l < *r; // Invokes your Dog's operator <
}
};
Then
map<sptr, int, MyCompare> m;
m[make_shared<Dog>("A")] = 1;
m[make_shared<Dog>("B")] = 2;
m[make_shared<Dog>("C")] = 3;
m[make_shared<Dog>("A")] = 4;
cout << "map size: " << m.size() << endl;
outputs map size: 3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With