web-dev-qa-db-fra.com

Pointeurs comme clés dans la carte C ++ STL

J'ai une question sur la façon dont les pointeurs vers un objet personnalisé sont gérés lorsqu'ils sont utilisés comme clés dans une carte. Plus précisément si je définis

std::map< CustomClass*, int > foo;

L'implémentation C++ par défaut fonctionnerait-elle pour gérer ces pointeurs? Ou dois-je définir une fonction de comparaison personnalisée pour la gérer? En général, est-ce une bonne pratique d'utiliser des pointeurs vers des objets comme clés?

34
Ammar Husain

L'implémentation par défaut comparera les adresses stockées par les pointeurs, donc différents objets seront considérés comme des clés différentes. Cependant, l'état logique de l'objet ne sera pas pris en compte. Par exemple, si vous utilisez std::string * Comme clé, deux objets std::string Différents avec le même texte de "Hello" Seraient considérés comme une clé différente! (Lorsque stocké dans la carte par leurs adresses)

Vous pouvez utiliser des pointeurs comme clés tant que vous comprenez la différence importante ci-dessus.

50
Neil Kirk

Les pointeurs seront traités mais comparés comme des pointeurs (ordre de la mémoire). Vous devez passer un foncteur less personnalisé si vous souhaitez comparer les objets:

template<class T> struct ptr_less {
    bool operator()(T* lhs, T* rhs) {
        return *lhs < *rhs; }};
map<Object*,int,ptr_less<Object>> mymap;
22
firda

La norme C++ fournit une spécialisation de std::less pour les pointeurs, donc oui, vous pouvez les utiliser en toute sécurité comme clés de carte, etc.

7
Wojtek Surowka

En dehors de la légalité de ce problème et de tout malentendu sémantique éventuel, déjà abordé, je ne vois aucune raison d'utiliser std::map ici plutôt que std::unordered_map. Il existe des interceptions précoces de cela dans Boost et Visual C++ si vous êtes sur un compilateur pré-C++ 11.

Comme vous semblez utiliser un pointeur pour représenter un objet unique, quelque chose comme boost :: flyweight pourrait être applicable.

3
Steve Townsend

Les pointeurs peuvent être utilisés comme clés mais surtout avec un std :: map (ou std :: set) je ne le conseillerais pas. Le comportement du programme n'est pas déterministe, c'est-à-dire que lorsque l'on parcourt la carte, l'ordre dans lequel les éléments de la carte sont itérés n'est pas garanti d'être le même . Cela dépend vraiment de l'adresse mémoire de l'objet (clé). Jetez un œil à cet exemple, comme vous pouvez le voir, quel que soit l'ordre d'insertion dans la carte, les éléments sont itérés de manière déterministe lorsque la clé est une chaîne plutôt qu'un pointeur.

http://ideone.com/VKirct

#include <iostream>
#include <map>
using namespace std;

class SomeClass {
    public:
    SomeClass(const std::string& name): m_name(name) {}
    std::string GetName()const {return m_name; }
    bool operator <(const SomeClass& rhs) const { return m_name < rhs.m_name; }
    private:
    std::string m_name;
};

auto print_seq  = [](const auto& seq) { for (const auto& itr: seq) {std::cout << itr.second << " , ";} std::cout << std::endl;};

int main() {
    // your code goes here
    std::map<SomeClass*, std::string> pointer_keyed_map;
    SomeClass s3("object3");
    SomeClass s1("object1");
    SomeClass s2("object2");
    pointer_keyed_map.insert(std::make_pair(&s1, s1.GetName()));
    pointer_keyed_map.insert(std::make_pair(&s2, s2.GetName()));
    pointer_keyed_map.insert(std::make_pair(&s3, s3.GetName()));
    std::cout << "Pointer based keys: object order" << std::endl;
    print_seq(pointer_keyed_map);

    std::map<SomeClass, std::string> int_keyed_map;
    int_keyed_map.insert(std::make_pair(s3, s3.GetName()));
    int_keyed_map.insert(std::make_pair(s1, s1.GetName()));
    int_keyed_map.insert(std::make_pair(s2, s2.GetName()));
    std::cout << "String based keys: object order" << std::endl;
    print_seq(int_keyed_map);
    return 0;
}
3
blueskin