Pourquoi l'argument de type de carte C ++ nécessite-t-il un constructeur vide lors de l'utilisation de []?

99

Voir aussi liste standard C ++ et types constructibles par défaut

Pas un problème majeur, juste ennuyeux car je ne veux pas que ma classe soit jamais instanciée sans les arguments particuliers.

#include <map>

struct MyClass
{
    MyClass(int t);
};

int main() {
    std::map<int, MyClass> myMap;
    myMap[14] = MyClass(42);
}

Cela me donne l'erreur g ++ suivante:

/usr/include/c++/4.3/bits/stl_map.h:419: erreur: aucune fonction correspondante pour l'appel à 'MyClass ()'

Cela compile bien si j'ajoute un constructeur par défaut; Je suis certain que cela n'est pas dû à une syntaxe incorrecte.

Nick Bolton
la source
Le code ci-dessus se compile très bien sur MinGW (g ++ 3.4.5) et MSVC ++ 2008, à condition qu'un typedef pour MyType soit donné et un point-virgule ajouté à la fin de la classe. Vous devez faire autre chose (par exemple appeler l'opérateur [] comme mentionné par bb) - veuillez poster le code complet .
j_random_hacker
Ah oui, tu as raison. Ça ira.
Nick Bolton
Ouais, sans utilisation de myMap, vous ne savez pas ce qui doit être compilé pour la classe de carte. Le fournisseur et la version de la bibliothèque stl peuvent également aider.
Greg Domjan

Réponses:

165

Ce problème vient avec l'opérateur []. Citation de la documentation SGI:

data_type& operator[](const key_type& k)- Renvoie une référence à l'objet associé à une clé particulière. Si la carte ne contient pas déjà un tel objet, operator[] insère l'objet par défaut data_type().

Si vous n'avez pas de constructeur par défaut, vous pouvez utiliser les fonctions d'insertion / recherche. L'exemple suivant fonctionne bien:

myMap.insert( std::map< int, MyClass >::value_type ( 1, MyClass(1) ) );
myMap.find( 1 )->second;
bayda
la source
11
Excellente réponse - notez également emplaceen C ++ 11 comme alternative laconique à insert.
prideout
3
Pourquoi est-ce std::<map>::value_typelà dans l' insertappel?
thomthom
1
Pourquoi le constructeur par défaut doit-il être défini par l'utilisateur?
schuess
@schuess Je ne vois aucune raison pour laquelle cela fonctionne: = defaultdevrait fonctionner très bien.
underscore_d
La condition «La carte ne contient pas déjà un tel objet» serait évaluée lors de l'exécution. Pourquoi une erreur de compilation?
Gaurav Singh
7

Oui. Les valeurs des conteneurs STL doivent conserver la sémantique de copie. IOW, ils doivent se comporter comme des types primitifs (par exemple int) ce qui signifie, entre autres, qu'ils doivent être constructibles par défaut.

Sans cela (et d'autres exigences), il serait inutilement difficile d'implémenter les diverses opérations internes de copie / déplacement / échange / comparaison sur les structures de données avec lesquelles les conteneurs STL sont implémentés.

En référence à la norme C ++, je vois que ma réponse n'était pas exacte. La construction par défaut n'est en fait pas une exigence :

À partir du 20.1.4.1:

Le constructeur par défaut n'est pas requis. Certaines signatures de fonction de membre de classe de conteneur spécifient le constructeur par défaut comme argument par défaut. T () doit être une expression bien définie ...

Donc, à proprement parler, votre type de valeur n'a besoin d'être constructible par défaut que si vous utilisez une fonction du conteneur qui utilise le constructeur par défaut dans sa signature.

Les exigences réelles (23.1.3) de toutes les valeurs stockées dans les conteneurs STL sont CopyConstructibleet Assignable.

Il existe également d'autres exigences spécifiques pour des conteneurs particuliers, comme être Comparable(par exemple pour les clés d'une carte).


Incidemment, ce qui suit compile sans erreur sur comeau :

#include <map>

class MyClass
{
public:
    MyClass(int t);
};

int main()
{
    std::map<int, MyClass> myMap;
}

Cela pourrait donc être un problème g ++.

Assaf Lavie
la source
2
Pensez-vous que bb pourrait être sur quelque chose concernant l'opérateur []?
Nick Bolton
13
Ce code se compile probablement parce que vous n'appelez pas myMap []
jfritz42
3

Vérifiez les exigences du type stocké de stl :: map. De nombreuses collections stl nécessitent que le type stocké contienne des propriétés spécifiques (constructeur par défaut, constructeur de copie, etc.).

Le constructeur sans arguments est nécessaire à stl :: map, car il est utilisé lorsque l'opérateur [] est appelé avec la clé, qui n'a pas déjà été conservée par la carte. Dans ce cas, l'opérateur [] insère la nouvelle entrée constituée de la nouvelle clé et de la nouvelle valeur construite à l'aide d'un constructeur sans paramètre. Et cette nouvelle valeur est ensuite renvoyée.

oo_olo_oo
la source
-2

Vérifier si:

  • Vous avez oublié le ';' après la déclaration de classe.
  • MyType aurait dû être déclaré en conséquence.
  • Pas de constructeur par défaut là-bas ...

La déclaration std :: map semble correcte, je pense.

Hernán
la source
Compile bien si j'ajoute un constructeur par défaut.
Nick Bolton
-2

Très probablement parce que std :: pair l'exige. std :: pair contient deux valeurs en utilisant la sémantique des valeurs, vous devez donc pouvoir les instancier sans paramètres. Ainsi, le code utilise std :: pair à divers endroits pour renvoyer les valeurs de la carte à l'appelant et cela se fait généralement en instanciant une paire vide et en lui affectant les valeurs avant de renvoyer la paire locale.

Vous pouvez contourner cela avec des pointeurs intelligents en utilisant une carte <int, smartptr <MyClass>> mais cela ajoute la surcharge de la vérification des pointeurs nuls.

jmucchiello
la source
2
+0. pair <T, U> peut être utilisé très bien avec les types T et U dépourvus de constructeurs par défaut - la seule chose qui ne peut pas être utilisée dans ce cas est le constructeur par défaut de pair <T, U>. Aucune implémentation de qualité décente de map <K, V> n'utiliserait ce constructeur par défaut car il limite ce que K et V peuvent être.
j_random_hacker