web-dev-qa-db-fra.com

std :: maps avec les types définis par l'utilisateur comme clé

Je me demande pourquoi je ne peux pas utiliser de cartes STL avec des classes définies par l'utilisateur. Lorsque je compile le code ci-dessous, j'obtiens ce message d'erreur cryptique. Qu'est-ce que ça veut dire? Aussi, pourquoi cela ne se produit-il qu'avec des types définis par l'utilisateur? (les types primitifs sont acceptables quand il est utilisé pour la clé)

C:\MinGW\bin ..\lib\gcc\mingw32\3.4.5 ........\include\c ++\3.4.5\bits\stl_function.h || In fonction membre `bool std :: less <_Tp> :: operator () (const _Tp &, const _Tp &) const [avec _Tp = Class1] ': |

C:\MinGW\bin ..\lib\gcc\mingw32\3.4.5 ........\include\c ++\3.4.5\bits\stl_map.h | 338 | instancié de `_Tp & std :: map <_Key, _Tp, _Compare, _Alloc> :: operator [] (const _Key &) [avec _Key = Classe1, _Tp = int, _Compare = std :: less, _Alloc = std :: allocator>] '|

C:\Utilisateurs\Admin\Documents\dev\sandbox\sandbox\sandbox.cpp | 24 | instancié d'ici |

C:\MinGW\bin ..\lib\gcc\mingw32\3.4.5 ........\include\c ++\3.4.5\bits\stl_function.h | 227 | erreur: aucune correspondance pour l'opérateur ' <'in' __x < __y '| || === Construction terminée: 1 erreurs, 0 avertissements === |

#include <iostream>
#include <map>

using namespace std;

class Class1
{
public:
    Class1(int id);

private:
    int id;
};

Class1::Class1(int id): id(id)
{}

int main()
{
    Class1 c1(1);

    map< Class1 , int> c2int;
    c2int[c1] = 12;

    return 0;
}
49
unknown

En fait, vous n'avez pas (ont} _ pour définir operator< pour votre classe. Vous pouvez également créer une classe d'objets fonction de comparateur pour cette classe et l'utiliser pour spécialiser std::map. Pour prolonger votre exemple:

struct Class1Compare
{
   bool operator() (const Class1& lhs, const Class1& rhs) const
   {
       return lhs.id < rhs.id;
   }
};

std::map<Class1, int, Class1Compare> c2int;

Il se trouve que la valeur par défaut du troisième paramètre de modèle, std::map, est std::less , qui délègue à operator< défini pour votre classe (et échoue s’il n’en existe pas). Mais parfois, vous voulez que les objets soient utilisables en tant que clés de carte, mais vous n'avez pas réellement de sémantique de comparaison {significative}, et vous ne voulez donc pas confondre les gens en fournissant operator< à votre classe rien que pour ça. Si c'est le cas, vous pouvez utiliser l'astuce ci-dessus.

Une autre méthode consiste à spécialiser std::less:

namespace std
{
    template<> struct less<Class1>
    {
       bool operator() (const Class1& lhs, const Class1& rhs) const
       {
           return lhs.id < rhs.id;
       }
    };
}

L'avantage de cela est qu'il sera sélectionné par std::map "par défaut", sans pour autant exposer operator< au code client.

123
Pavel Minaev

Par défaut std::map (et std::set ) utilisez operator< pour déterminer le tri. Par conséquent, vous devez définir operator< sur votre classe.

Deux objets sont réputés équivalentif !(a < b) && !(b < a).

Si, pour une raison quelconque, vous souhaitez utiliser un comparateur différent, le troisième argument de modèle de la variable map peut être remplacé, par exemple par std::greater .

20
GManNickG

Vous devez définir operator < pour Class1.

La carte doit comparer les valeurs à l'aide de l'opérateur <et vous devez donc fournir la même chose lorsque la classe définie par l'utilisateur est utilisée comme clé.

class Class1
{
public:
    Class1(int id);

    bool operator <(const Class1& rhs) const
    {
        return id < rhs.id;
    }
private:
    int id;
};
12
aJ.

Les clés doivent être comparables, mais vous n'avez pas défini de operator< approprié pour votre classe personnalisée.

3
unwind
class key
{
    int m_value;
public:
    bool operator<(const key& src)const
    {
        return (this->m_value < src.m_value);
    }

};
int main()
{
    key key1;
    key key2;
    map<key,int> mymap;
    mymap.insert(pair<key,int>(key1,100));
    mymap.insert(pair<key,int>(key2,200));
    map<key,int>::iterator iter=mymap.begin();
    for(;iter!=mymap.end();++iter)
    {
        cout<<iter->second<<endl;
    }


}
3
Kaushal

La bonne solution est de spécialiser std::less pour votre classe/Struct.

• Fondamentalement, les cartes dans cpp sont implémentées en tant qu'arbres de recherche binaires.

  1. Les BST comparent des éléments de nœuds pour déterminer l'organisation de l'arborescence.
  2. Les nœuds dont l’élément compare moins que celui du nœud parent sont placés à gauche du parent et les nœuds dont les éléments sont supérieurs à l’élément nœuds parent sont placés à droite . i.e.

Node.left.key <node.key <node.right.key pour chaque noeud

Chaque nœud du BST contient des éléments et, en cas de mappage, sa clé et une valeur. Les clés sont censées être commandées. Plus d'informations sur l'implémentation de la carte: Type de données de la carte .

Dans le cas de cartes cpp, les clés sont les éléments des nœuds et les valeurs ne participent pas à l'organisation de l'arborescence, il s'agit simplement d'une donnée supplémentaire.

Cela signifie donc que les clés doivent être compatibles avec std::less ou operator< pour pouvoir être organisées. S'il vous plaît vérifier paramètres de la carte .

Sinon, si vous utilisez le type de données défini par l'utilisateur comme clé, vous devez donner à la signification une sémantique de comparaison complète pour ce type de données.

Solution: Spécialisation std::less:

Le troisième paramètre du modèle de mappe est facultatif et c’est std::less qui déléguera à operator<,

Créez donc un nouveau std::less pour votre type de données défini par l'utilisateur. Maintenant, ce nouveau std::less sera choisi par std::map par défaut.

namespace std
{
    template<> struct  less<MyClass>
    {
        bool operator() (const MyClass& lhs, const MyClass& rhs) const
        {
            return lhs.anyMemen < rhs.age;
        }
    };

}

Remarque: Vous devez créer un std::less spécialisé pour chaque type de données défini par l'utilisateur (si vous souhaitez utiliser ce type de données comme clé pour les mappes cpp).

Solution incorrecte: Surcharge operator< pour votre type de données défini par l'utilisateur . Cette solution fonctionnera également mais sa très mauvaise comme opérateur < sera surchargée universellement pour votre type/classe de données. indésirable dans les scénarios client.

Veuillez vérifier la réponse La réponse de Pavel Minaev

0
BreakBadSP

Je voudrais développer un peu sur Pavel Minaev answer , que vous devriez lire avant de lire ma réponse. Les deux solutions présentées par Pavel ne seront pas compilées si le membre à comparer (tel que id dans le code de la question) est privé. Dans ce cas, VS2013 génère l'erreur suivante pour moi:

erreur C2248: 'Class1 :: id': impossible d'accéder au membre privé déclaré dans la classe 'Class1'

Comme mentionné par SkyWalker dans les commentaires on Pavel answer, utiliser une déclaration friend aide. Si vous vous interrogez sur la syntaxe correcte, la voici:

class Class1
{
public:
    Class1(int id) : id(id) {}

private:
    int id;
    friend struct Class1Compare;      // Use this for Pavel's first solution.
    friend struct std::less<Class1>;  // Use this for Pavel's second solution.
};

Code sur Ideone

Toutefois, si vous avez une fonction d'accès pour votre membre privé, par exemple getId() pour id, comme suit:

class Class1
{
public:
    Class1(int id) : id(id) {}
    int getId() const { return id; }

private:
    int id;
};

alors vous pouvez l’utiliser au lieu d’une déclaration friend (c’est-à-dire que vous comparez lhs.getId() < rhs.getId()). Depuis C++ 11 , vous pouvez également utiliser une expression lambda pour la première solution de Pavel au lieu de définir une classe d'objets fonction de comparateur. En mettant tout ensemble, le code pourrait être écrit comme suit:

auto comp = [](const Class1& lhs, const Class1& rhs){ return lhs.getId() < rhs.getId(); };
std::map<Class1, int, decltype(comp)> c2int(comp);

Code sur Ideone

0
honk