2014-10-08 17 views
9

Ho il seguente codice per testare il puntatore intelligente come chiave per std::map, eseguo il codice su Mac e Linux, ma ho osservato diversi output, è un bug o ho fatto qualcosa di sbagliato?Puntatori intelligenti come chiave di mappa

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

Su Mac:

nee[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 

Su 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, il secondo operatore '<' non ha bisogno di essere un amico, può essere semplicemente 'return * lhs <* rhs;' – o11c

+0

@ o11c, sì, ma il cambiamento non fa differenza. – neevek

+0

Eventualmente correlato: http://stackoverflow.com/questions/11115265/clang-stdshared-ptr-and-stdless-operator –

risposta

8

GCC è di destra (su Mac, come si vede g++ è infatti clang): std::map utilizza std::less<T> per confrontare le chiavi. Ciò a sua volta chiama operator < sugli argomenti, ma la ricerca viene eseguita per la prima volta in namespace std, quindi trova l'implementazione predefinita per shared_ptr, confrontando i puntatori interni. Per fare questo lavoro bisogna specializzarsi std::less per 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. Il fatto divertente è che se questo era al di fuori dello spazio dei nomi 'std', avrebbe funzionato al contrario - l'operatore' OP <'non è un template, mentre' std :: operator <'è. – Angew

+0

Grazie per la rapida risposta e una chiara spiegazione, funziona! – neevek

2

L'impostazione predefinita Compare oggetto di std::map è std::less<Key>, che è std::shared_ptr<Dog> nel tuo caso. Quindi cerca l'implementazione std::less< std::shared_ptr<Dog> > e confronta solo l'indirizzo del puntatore.

Per specificare l'oggetto Compare, si può solo definire da soli e utilizzarlo in map

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

Poi

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; 

uscite map size: 3

Problemi correlati