Warum erfordert das Argument vom Typ C ++ - Zuordnungstyp einen leeren Konstruktor, wenn [] verwendet wird?

98

Siehe auch C ++ - Standardliste und standardmäßig konstruierbare Typen

Kein großes Problem, nur ärgerlich, da ich nicht möchte, dass meine Klasse jemals ohne die besonderen Argumente instanziiert wird.

#include <map>

struct MyClass
{
    MyClass(int t);
};

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

Dies gibt mir den folgenden g ++ Fehler:

/usr/include/c++/4.3/bits/stl_map.h:419: Fehler: Keine passende Funktion für den Aufruf von 'MyClass ()'

Dies lässt sich gut kompilieren, wenn ich einen Standardkonstruktor hinzufüge. Ich bin sicher, dass es nicht durch falsche Syntax verursacht wird.

Nick Bolton
quelle
Der obige Code lässt sich problemlos in MinGW (g ++ 3.4.5) und MSVC ++ 2008 kompilieren, vorausgesetzt, ein Typedef für MyType ist angegeben und ein Semikolon wird an das Ende der Klasse angehängt. Sie müssen etwas anderes tun (z. B. Operator [] anrufen, wie von bb erwähnt) - bitte geben Sie den vollständigen Code ein.
j_random_hacker
Ah ja, du hast recht. Wird besorgt.
Nick Bolton
Ja, ohne die Verwendung von myMap wissen Sie nicht, was für die Kartenklasse kompiliert werden muss. Welcher STL-Bibliotheksanbieter und welche Version könnten ebenfalls hilfreich sein?
Greg Domjan

Antworten:

165

Dieses Problem tritt mit operator [] auf. Zitat aus der SGI-Dokumentation:

data_type& operator[](const key_type& k)- Gibt einen Verweis auf das Objekt zurück, das einem bestimmten Schlüssel zugeordnet ist. Wenn die Karte noch kein solches Objekt enthält, wird operator[] das Standardobjekt eingefügt data_type().

Wenn Sie keinen Standardkonstruktor haben, können Sie Einfüge- / Suchfunktionen verwenden. Das folgende Beispiel funktioniert einwandfrei:

myMap.insert( std::map< int, MyClass >::value_type ( 1, MyClass(1) ) );
myMap.find( 1 )->second;
Bayda
quelle
11
Hervorragende Antwort - auch emplacein C ++ 11 als knappe Alternative zu beachten insert.
Stolz
3
Warum ist das std::<map>::value_typedort im insertAnruf?
thomthom
1
Warum muss der Standardkonstruktor benutzerdefiniert sein?
schuess
@schuess Ich sehe keinen Grund warum es funktioniert: = defaultsollte gut funktionieren.
underscore_d
Die Bedingung 'Karte enthält noch kein solches Objekt' wird zur Laufzeit ausgewertet. Warum ein Fehler bei der Kompilierung?
Gaurav Singh
8

Ja. Werte in STL-Containern müssen die Kopiersemantik beibehalten. IOW, sie müssen sich wie primitive Typen (z. B. int) verhalten, was unter anderem bedeutet, dass sie standardmäßig konstruierbar sein sollten.

Ohne diese (und andere Anforderungen) wäre es unnötig schwierig, die verschiedenen internen Operationen zum Kopieren / Verschieben / Austauschen / Vergleichen in den Datenstrukturen zu implementieren, mit denen STL-Container implementiert sind.

Unter Bezugnahme auf den C ++ - Standard sehe ich, dass meine Antwort nicht korrekt war. Die Standardkonstruktion ist in der Tat keine Voraussetzung :

Ab 20.1.4.1:

Der Standardkonstruktor ist nicht erforderlich. Bestimmte Funktionssignaturen von Containerklassenmitgliedern geben den Standardkonstruktor als Standardargument an. T () muss ein genau definierter Ausdruck sein ...

Genau genommen muss Ihr Wertetyp nur dann standardmäßig konstruierbar sein, wenn Sie zufällig eine Funktion des Containers verwenden, der den Standardkonstruktor in seiner Signatur verwendet.

Die tatsächlichen Anforderungen (23.1.3) an alle in STL-Containern gespeicherten Werte sind CopyConstructibleund Assignable.

Es gibt auch andere spezifische Anforderungen für bestimmte Container, z. B. Comparable(z. B. für Schlüssel in einer Karte).


Übrigens wird folgendes auf comeau fehlerfrei kompiliert :

#include <map>

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

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

Dies könnte also ein G ++ - Problem sein.

Assaf Lavie
quelle
2
Glaubst du, bb könnte etwas mit dem Operator [] zu tun haben?
Nick Bolton
12
Dieser Code wird wahrscheinlich kompiliert, weil Sie myMap []
jfritz42
3

Überprüfen Sie die Anforderungen des gespeicherten Typs der stl :: map. Viele stl-Auflistungen erfordern, dass der gespeicherte Typ einige spezifische Eigenschaften enthält (Standardkonstruktor, Kopierkonstruktor usw.).

Der Konstruktor ohne Argumente wird von der stl :: map benötigt, da er verwendet wird, wenn operator [] mit dem Schlüssel aufgerufen wird, der noch nicht von der Map beibehalten wurde. In diesem Fall fügt der Operator [] den neuen Eintrag ein, der aus dem neuen Schlüssel und dem Wert besteht, die mit dem parameterlosen Konstruktor erstellt wurden. Und dieser neue Wert wird dann zurückgegeben.

oo_olo_oo
quelle
-2

Überprüfen Sie, ob:

  • Du hast das ';' nach Klassendeklaration.
  • MyType sollte entsprechend deklariert worden sein.
  • Kein Standardkonstruktor da ...

Die std :: map-Deklaration scheint korrekt zu sein, denke ich.

Hernán
quelle
Kompiliert gut, wenn ich einen Standardkonstruktor hinzufüge.
Nick Bolton
-2

Höchstwahrscheinlich, weil std :: pair dies erfordert. std :: pair enthält zwei Werte unter Verwendung der Wertesemantik, sodass Sie sie ohne Parameter instanziieren können müssen. Daher verwendet der Code an verschiedenen Stellen std :: pair, um die Kartenwerte an den Aufrufer zurückzugeben. Dies geschieht normalerweise, indem ein leeres Paar instanziiert und die Werte zugewiesen werden, bevor das lokale Paar zurückgegeben wird.

Sie könnten dies mit intelligenten Zeigern umgehen, indem Sie eine Karte <int, smartptr <MyClass>> verwenden. Dies erhöht jedoch den Aufwand für die Suche nach Nullzeigern.

jmucchiello
quelle
2
+0. Das Paar <T, U> kann problemlos mit den Typen T und U verwendet werden, denen Standardkonstruktoren fehlen. Das einzige, was in diesem Fall nicht verwendet werden kann, ist der eigene Standardkonstruktor des Paares <T, U>. Keine Implementierung von Map <K, V> mit anständiger Qualität würde diesen Standardkonstruktor verwenden, da er die Möglichkeiten von K und V einschränkt.
j_random_hacker