Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smart pointers as map key

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
like image 211
neevek Avatar asked Oct 08 '14 08:10

neevek


People also ask

Can pointer be key of a map?

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.

When should smart pointers be used?

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.

What's the difference between pointers and smart pointers?

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.

Are smart pointers necessary?

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.


2 Answers

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;
        }
    };
}
like image 160
Anton Savin Avatar answered Sep 25 '22 09:09

Anton Savin


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

like image 44
Mine Avatar answered Sep 25 '22 09:09

Mine