¿Por qué el argumento del tipo de mapa de C ++ requiere un constructor vacío cuando se usa []?

98

Consulte también la lista estándar de C ++ y los tipos construibles por defecto

No es un problema importante, solo es molesto, ya que no quiero que se cree una instancia de mi clase sin los argumentos particulares.

#include <map>

struct MyClass
{
    MyClass(int t);
};

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

Esto me da el siguiente error de g ++:

/usr/include/c++/4.3/bits/stl_map.h:419: error: no hay función coincidente para llamar a 'MyClass ()'

Esto se compila bien si agrego un constructor predeterminado; Estoy seguro de que no es causado por una sintaxis incorrecta.

Nick Bolton
fuente
El código anterior se compila muy bien en MinGW (g ++ 3.4.5) y MSVC ++ 2008, siempre que se proporcione un typedef para MyType y se agregue un punto y coma al final de la clase. Debe estar haciendo otra cosa (por ejemplo, llamar al operador [] como lo menciona bb); publique el código completo .
j_random_hacker
Ah, sí, tienes razón. Lo haré.
Nick Bolton
Sí, sin el uso de myMap, no sabe qué debe compilarse para la clase de mapa. Qué proveedor de biblioteca stl y qué versión también podrían ayudar.
Greg Domjan

Respuestas:

165

Este problema viene con el operador []. Cita de la documentación de SGI:

data_type& operator[](const key_type& k)- Devuelve una referencia al objeto que está asociado con una clave en particular. Si el mapa aún no contiene tal objeto, operator[] inserta el objeto predeterminado data_type().

Si no tiene un constructor predeterminado, puede usar funciones de inserción / búsqueda. El siguiente ejemplo funciona bien:

myMap.insert( std::map< int, MyClass >::value_type ( 1, MyClass(1) ) );
myMap.find( 1 )->second;
Bayda
fuente
11
Excelente respuesta: tenga emplaceen cuenta también en C ++ 11 como una alternativa concisa a insert.
orgullo
3
¿Por qué hay eso std::<map>::value_typeen la insertllamada?
thomthom
1
¿Por qué el constructor predeterminado debe ser definido por el usuario?
sábado
@schuess No veo ninguna razón por la que lo haga: = defaultdebería funcionar bien.
underscore_d
La condición 'El mapa aún no contiene tal objeto' se evaluaría en tiempo de ejecución. ¿Por qué un error de tiempo de compilación?
Gaurav Singh
8

Si. Los valores de los contenedores STL deben mantener la semántica de copia. IOW, deben comportarse como tipos primitivos (por ejemplo, int) lo que significa, entre otras cosas, que deben ser construibles por defecto.

Sin este (y otros requisitos), sería innecesariamente difícil implementar las diversas operaciones internas de copia / movimiento / intercambio / comparación en las estructuras de datos con las que se implementan los contenedores STL.

Al hacer referencia al estándar C ++, veo que mi respuesta no fue precisa. La construcción por defecto no es, de hecho, un requisito :

Desde 20.1.4.1:

El constructor predeterminado no es necesario. Ciertas firmas de funciones de miembros de clases de contenedores especifican el constructor predeterminado como argumento predeterminado. T () debe ser una expresión bien definida ...

Entonces, estrictamente hablando, su tipo de valor solo necesita ser construible predeterminado si está usando una función del contenedor que usa el constructor predeterminado en su firma.

Los requisitos reales (23.1.3) de todos los valores almacenados en contenedores STL son CopyConstructibley Assignable.

También existen otros requisitos específicos para contenedores particulares, como ser Comparable(por ejemplo, para las claves en un mapa).


Por cierto, lo siguiente se compila sin errores en comeau :

#include <map>

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

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

Entonces esto podría ser un problema de g ++.

Assaf Lavie
fuente
2
¿Crees que bb podría estar relacionado con el operador []?
Nick Bolton
12
Ese código probablemente se compila porque no está llamando a myMap []
jfritz42
3

Verifique los requisitos del tipo almacenado de stl :: map. Muchas colecciones stl requieren que el tipo almacenado contenga algunas propiedades específicas (constructor predeterminado, constructor de copia, etc.).

El stl :: map necesita un constructor sin argumentos, porque se usa cuando se invoca al operador [] con la clave, que aún no ha sido guardada por el mapa. En este caso, el operador [] inserta la nueva entrada que consiste en la nueva clave y el valor construidos usando un constructor sin parámetros. Y luego se devuelve este nuevo valor.

oo_olo_oo
fuente
-2

Comprobar si:

  • Olvidaste el ';' después de la declaración de clase.
  • MyType debería haberse declarado en consecuencia.
  • No hay constructor predeterminado allí ...

La declaración std :: map parece correcta, creo.

Hernán
fuente
Se compila bien si agrego un constructor predeterminado.
Nick Bolton
-2

Probablemente porque std :: pair lo requiere. std :: pair contiene dos valores usando semántica de valor, por lo que debe poder instanciarlos sin parámetros. Entonces, el código usa std :: pair en varios lugares para devolver los valores del mapa a la persona que llama y esto se hace comúnmente instanciando un par vacío y asignándole los valores antes de devolver el par local.

Puede evitar esto con punteros inteligentes usando un mapa <int, smartptr <MyClass>> pero eso agrega la sobrecarga de verificar punteros nulos.

jmucchiello
fuente
2
+0. pair <T, U> puede usarse bien con los tipos T y U que carecen de constructores predeterminados; lo único que no se puede usar en este caso es el propio constructor predeterminado de pair <T, U>. Ninguna implementación de calidad decente del mapa <K, V> usaría este constructor predeterminado porque limita lo que pueden ser K y V.
j_random_hacker