2014-10-08 10 views
9

Ich habe den folgenden Code, um Smart Pointer als Schlüssel für std::map zu testen, ich den Code auf Mac und Linux, aber ich beobachtete unterschiedliche Ausgabe, ist es ein Fehler oder habe ich etwas falsch gemacht?Smart Zeiger als Kartenschlüssel

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

Auf 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 

Unter 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

btw, den zweiten 'Operator <' muss kein Freund sein, es kann einfach 'return * lhs <* rhs;' – o11c

+0

@ o11c sein, ja, aber die Änderung macht keinen Unterschied. – neevek

+0

Möglicherweise verwandt: http://stackoverflow.com/questions/11115265/clang-stdshared-ptr-and-stdless-operator –

Antwort

8

GCC ist rechts (auf Mac wie Sie sehen g++ in der Tat Klirren ist): std::map verwendet std::less<T> Schlüssel zu vergleichen. Das wiederum ruft operator < für die Argumente auf, aber die Suche wird zuerst in namespace std ausgeführt, sodass es die Standardimplementierung für shared_ptr findet, die interne Zeiger vergleicht. Um diese Arbeit zu machen, müssen Sie spezialisieren std::less für 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. Tatsache ist, dass wenn das außerhalb des Namespace 'std' liegt, es umgekehrt funktionieren würde - der' operator <'des OPs ist keine Vorlage, während der' std :: operator <'ist. – Angew

+0

Danke für die schnelle Antwort und klare Erklärung, es funktioniert! – neevek

2

Der Standard Compare Objekt von std::map ist std::less<Key>, die std::shared_ptr<Dog> in Ihrem Fall ist. So sucht es nach std::less< std::shared_ptr<Dog> > Implementierung, und es vergleicht nur die Zeigeradresse.

Um das Compare Objekt angeben, können Sie selbst festlegen es einfach und verwenden Sie es in map

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

Dann

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; 

Ausgänge map size: 3