2014-10-08 2 views
9

У меня есть следующий код для проверки умного указателя как ключа для std::map, я запускаю код на Mac и Linux, но я наблюдал различный вывод, это ошибка или я сделал что-то не так?Умные указатели как ключ карты

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

На Mac:

[email protected]$ 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 

[email protected]$ ./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 

В Linux:

[email protected]$ 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. 

[email protected]$ ./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 
+0

Кстати, второй 'оператор <' не нужно быть другом, он может просто быть «return * lhs <* rhs;» – o11c

+0

@ o11c, да, но изменение не имеет значения. – neevek

+0

Возможно, связано: http://stackoverflow.com/questions/11115265/clang-stdshared-ptr-and-stdless-operator –

ответ

8

НКА права (на Mac, как вы видите g++ это на самом деле звон): std::map использует std::less<T> для сравнения ключей. Это в свою очередь вызывает operator < по аргументам, но поиск выполняется сначала в namespace std, поэтому он находит реализацию по умолчанию для shared_ptr, сравнивая внутренние указатели. Для того, чтобы сделать эту работу, вы должны специализироваться std::less для 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; 
     } 
    }; 
} 
+0

+1. Интересно, что если бы это было за пределами пространства имен 'std', это сработало бы наоборот: OP-оператор' 'не является шаблоном, а' std :: operator <'is. – Angew

+0

Спасибо за быстрый ответ и ясное объяснение, оно работает! – neevek

2

Значение по умолчанию Compare объект std::map является std::less<Key>, что в вашем случае std::shared_ptr<Dog>. Таким образом, он ищет реализацию std::less< std::shared_ptr<Dog> >, и он просто сравнивает адрес указателя.

Чтобы указать Compare объект, вы можете просто определить его самостоятельно и использовать его в map

class MyCompare { 
public: 
    bool operator() (const sptr& l, const sptr& r) { 
    return *l < *r; // Invokes your Dog's operator < 
    } 
}; 

Тогда

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; 

выходы map size: 3

Смежные вопросы