¿Cómo inicializar un mapa constante estático privado en C ++?

108

Solo necesito diccionario o matriz asociativa string=> int.

Hay un mapa de tipos C ++ para este caso.

Pero solo necesito un mapa para todas las instancias (-> estático) y este mapa no se puede cambiar (-> const);

He encontrado esta manera con la biblioteca boost

 std::map<int, char> example = 
      boost::assign::map_list_of(1, 'a') (2, 'b') (3, 'c');

¿Hay otra solución sin esta lib? He intentado algo como esto, pero siempre hay algunos problemas con la inicialización del mapa.

class myClass{
private:
    static map<int,int> create_map()
        {
          map<int,int> m;
          m[1] = 2;
          m[3] = 4;
          m[5] = 6;
          return m;
        }
    static map<int,int> myMap =  create_map();

};
Meloun
fuente
1
¿Cuáles son los problemas a los que se refiere? ¿Está intentando utilizar este mapa de otra variable / constante estática global?
Péter Török
Eso no es una cadena de matriz asociativa => int, está mapeando un int a un char. v = k + 'a' - 1.
Johnsyweb

Respuestas:

107
#include <map>
using namespace std;

struct A{
    static map<int,int> create_map()
        {
          map<int,int> m;
          m[1] = 2;
          m[3] = 4;
          m[5] = 6;
          return m;
        }
    static const map<int,int> myMap;

};

const map<int,int> A:: myMap =  A::create_map();

int main() {
}

fuente
3
+1 por simplicidad, por supuesto, usar un Boost.Assigndiseño similar también es bastante bueno :)
Matthieu M.
5
+1, gracias. Nota: tuve que poner la línea de inicialización en mi archivo de implementación; dejarlo en el archivo de encabezado me dio errores debido a múltiples definiciones (el código de inicialización se ejecutaría siempre que el encabezado se incluyera en alguna parte).
System.Cats.Lol
1
Con g ++ v4.7.3, Esto compila, hasta que añado cout << A::myMap[1];en main(). Da un error. El error no ocurre si elimino los constcalificadores, por lo que supongo que los mapas operator[]no pueden manejar const map, al menos, no en la implementación g ++ de la biblioteca C ++.
Craig McQueen
2
El error es:const_map.cpp:22:23: error: passing ‘const std::map<int, int>’ as ‘this’ argument of ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const key_type&) [with _Key = int; _Tp = int; _Compare = std::less<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = int; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = int]’ discards qualifiers [-fpermissive]
Craig McQueen
4
De hecho, el operador del mapa [] no puede operar en un mapa constante porque ese operador crea la entrada referenciada si no existe (ya que devuelve una referencia al valor mapeado). C ++ 11 introdujo el método at (KeyValT key) que le permite acceder al elemento con una clave dada, lanzando una excepción si no existe. ( en.cppreference.com/w/cpp/container/map/at ) Este método funcionará en instancias const pero no se puede usar para insertar un elemento en una instancia no const (como hace el operador []).
mbargiel
108

El estándar C ++ 11 introdujo la inicialización uniforme que lo hace mucho más simple si su compilador lo admite:

//myClass.hpp
class myClass {
  private:
    static map<int,int> myMap;
};


//myClass.cpp
map<int,int> myClass::myMap = {
   {1, 2},
   {3, 4},
   {5, 6}
};

Consulte también esta sección de Professional C ++ , en unordered_maps.

David C. Bishop
fuente
¿Necesitamos el signo igual en el archivo cpp?
phoad
@phoad: El signo igual es superfluo.
Jinxed
Gracias por mostrar el uso. Fue realmente útil comprender cómo modificar las variables estáticas.
User9102d82
12

¡Lo hice! :)

Funciona bien sin C ++ 11

class MyClass {
    typedef std::map<std::string, int> MyMap;

    struct T {
        const char* Name;
        int Num;

        operator MyMap::value_type() const {
            return std::pair<std::string, int>(Name, Num);
        }
    };

    static const T MapPairs[];
    static const MyMap TheMap;
};

const MyClass::T MyClass::MapPairs[] = {
    { "Jan", 1 }, { "Feb", 2 }, { "Mar", 3 }
};

const MyClass::MyMap MyClass::TheMap(MapPairs, MapPairs + 3);
user2622030
fuente
11

Si lo encuentra boost::assign::map_list_ofútil, pero no puede usarlo por alguna razón, puede escribir el suyo propio :

template<class K, class V>
struct map_list_of_type {
  typedef std::map<K, V> Map;
  Map data;
  map_list_of_type(K k, V v) { data[k] = v; }
  map_list_of_type& operator()(K k, V v) { data[k] = v; return *this; }
  operator Map const&() const { return data; }
};
template<class K, class V>
map_list_of_type<K, V> my_map_list_of(K k, V v) {
  return map_list_of_type<K, V>(k, v);
}

int main() {
  std::map<int, char> example = 
    my_map_list_of(1, 'a') (2, 'b') (3, 'c');
  cout << example << '\n';
}

Es útil saber cómo funcionan estas cosas, especialmente cuando son tan cortas, pero en este caso usaría una función:

a.hpp

struct A {
  static map<int, int> const m;
};

a.cpp

namespace {
map<int,int> create_map() {
  map<int, int> m;
  m[1] = 2; // etc.
  return m;
}
}

map<int, int> const A::m = create_map();
Yu Hao
fuente
6

Un enfoque diferente al problema:

struct A {
    static const map<int, string> * singleton_map() {
        static map<int, string>* m = NULL;
        if (!m) {
            m = new map<int, string>;
            m[42] = "42"
            // ... other initializations
        }
        return m;
    }

    // rest of the class
}

Esto es más eficiente, ya que no hay una copia de un tipo de pila a pila (incluido el constructor y los destructores de todos los elementos). Si esto importa o no depende de su caso de uso. ¡No importa con cuerdas! (pero puede que encuentre esta versión "más limpia" o no)

ypnos
fuente
3
RVO elimina la copia en la respuesta mía y de Neil.
6

Si el mapa va a contener solo entradas que se conocen en el momento de la compilación y las claves del mapa son números enteros, entonces no es necesario utilizar un mapa en absoluto.

char get_value(int key)
{
    switch (key)
    {
        case 1:
            return 'a';
        case 2:
            return 'b';
        case 3:
            return 'c';
        default:
            // Do whatever is appropriate when the key is not valid
    }
}
Matthew T. Staebler
fuente
5
+1 por señalar que no se necesita un mapa, sin embargo, no puede iterar sobre esto
Viktor Sehr
4
Sin switchembargo, eso es horrible. ¿Por qué no return key + 'a' - 1?
Johnsyweb
12
@Johnsyweb. Supongo que el mapeo proporcionado por el cartel original se presentó únicamente como un ejemplo y no indicativo del mapeo real que tiene. Por lo tanto, también asumiría que eso return key + 'a' - 1no funcionaría para su mapeo real.
Matthew T. Staebler
3

Puedes probar esto:

MyClass.h

class MyClass {
private:
    static const std::map<key, value> m_myMap; 
    static const std::map<key, value> createMyStaticConstantMap();
public:
    static std::map<key, value> getMyConstantStaticMap( return m_myMap );
}; //MyClass

MyClass.cpp

#include "MyClass.h"

const std::map<key, value> MyClass::m_myMap = MyClass::createMyStaticConstantMap();

const std::map<key, value> MyClass::createMyStaticConstantMap() {
    std::map<key, value> mMap;
    mMap.insert( std::make_pair( key1, value1 ) );
    mMap.insert( std::make_pair( key2, value2 ) );
    // ....
    mMap.insert( std::make_pair( lastKey, lastValue ) ); 
    return mMap;
} // createMyStaticConstantMap

Con esta implementación, su mapa estático constante de clases es un miembro privado y puede ser accesible para otras clases usando un método get público. De lo contrario, dado que es constante y no puede cambiar, puede eliminar el método public get y mover la variable del mapa a la sección pública de clases. Sin embargo, dejaría el método createMap privado o protegido si se requiere herencia o polimorfismo. A continuación se muestran algunos ejemplos de uso.

 std::map<key,value> m1 = MyClass::getMyMap();
 // then do work on m1 or
 unsigned index = some predetermined value
 MyClass::getMyMap().at( index ); // As long as index is valid this will 
 // retun map.second or map->second value so if in this case key is an
 // unsigned and value is a std::string then you could do
 std::cout << std::string( MyClass::getMyMap().at( some index that exists in map ) ); 
// and it will print out to the console the string locted in the map at this index. 
//You can do this before any class object is instantiated or declared. 

 //If you are using a pointer to your class such as:
 std::shared_ptr<MyClass> || std::unique_ptr<MyClass>
 // Then it would look like this:
 pMyClass->getMyMap().at( index ); // And Will do the same as above
 // Even if you have not yet called the std pointer's reset method on
 // this class object. 

 // This will only work on static methods only, and all data in static methods must be available first.

Había editado mi publicación original, no había nada de malo con el código original en el que publiqué para que se compilara, construyera y ejecutara correctamente, era solo que en mi primera versión presenté como respuesta el mapa fue declarado como público y el mapa fue const pero no estático.

Francis Cugler
fuente
2

Si está utilizando un compilador que aún no admite la inicialización universal o tiene reservas para usar Boost, otra alternativa posible sería la siguiente

std::map<int, int> m = [] () {
    std::pair<int,int> _m[] = {
        std::make_pair(1 , sizeof(2)),
        std::make_pair(3 , sizeof(4)),
        std::make_pair(5 , sizeof(6))};
    std::map<int, int> m;
    for (auto data: _m)
    {
        m[data.first] = data.second;
    }
    return m;
}();
Abhijit
fuente
0

Una llamada a función no puede aparecer en una expresión constante.

prueba esto: (solo un ejemplo)

#include <map>
#include <iostream>

using std::map;
using std::cout;

class myClass{
 public:
 static map<int,int> create_map()
    {
      map<int,int> m;
      m[1] = 2;
      m[3] = 4;
      m[5] = 6;
      return m;
    }
 const static map<int,int> myMap;

};
const map<int,int>myClass::myMap =  create_map();

int main(){

   map<int,int> t=myClass::create_map();
   std::cout<<t[1]; //prints 2
}
Prasoon Saurav
fuente
6
Ciertamente, una función puede usarse para inicializar un objeto constante.
En OP, el código static map<int,int> myMap = create_map();es incorrecto.
Prasoon Saurav
3
El código en la pregunta es incorrecto, todos estamos de acuerdo con eso, pero no tiene nada que ver con las 'expresiones constantes' como dice en esta respuesta, sino con el hecho de que solo puede inicializar miembros estáticos constantes de una clase en el declaración si son de tipo entero o enumeración. Para todos los demás tipos, la inicialización debe realizarse en la definición de miembro y no en la declaración.
David Rodríguez - dribeas
La respuesta de Neil se compila con g ++. Aún así, recuerdo haber tenido algunos problemas con este enfoque en versiones anteriores de la cadena de herramientas GNU. ¿Existe una respuesta correcta universal?
Basilevs
1
@Prasoon: No sé lo que dice el compilador, pero el error en el código de la pregunta es inicializar un atributo de miembro constante del tipo de clase en la declaración de clase, independientemente de si la inicialización es una expresión constante o no. Si define una clase: struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;no se podrá compilar incluso si la inicialización se realiza con una expresión constante ( 5). Es decir, la 'expresión constante' es irrelevante para la corrección (o falta de ella) del código inicial.
David Rodríguez - dribeas
-2

A menudo uso este patrón y te recomiendo que lo uses también:

class MyMap : public std::map<int, int>
{
public:
    MyMap()
    {
        //either
        insert(make_pair(1, 2));
        insert(make_pair(3, 4));
        insert(make_pair(5, 6));
        //or
        (*this)[1] = 2;
        (*this)[3] = 4;
        (*this)[5] = 6;
    }
} const static my_map;

Seguro que no es muy legible, pero sin otras librerías es lo mejor que podemos hacer. Además, no habrá operaciones redundantes como copiar de un mapa a otro como en su intento.

Esto es aún más útil dentro de las funciones: en lugar de:

void foo()
{
   static bool initComplete = false;
   static Map map;
   if (!initComplete)
   {
      initComplete = true;
      map= ...;
   }
}

Utilice lo siguiente:

void bar()
{
    struct MyMap : Map
    {
      MyMap()
      {
         ...
      }
    } static mymap;
}

No solo ya no es necesario que esté aquí para tratar con la variable booleana, sino que ya no tendrá una variable global oculta que se verifique si el inicializador de la variable estática dentro de la función ya se llamó.

Pavel Chikulaev
fuente
6
La herencia debe ser la herramienta de último recurso, no la primera.
Un compilador que admite RVO elimina la copia redundante con las versiones de función. La semántica de movimiento C ++ 0x elimina el resto, una vez que están disponibles. En cualquier caso, dudo que esté cerca de ser un cuello de botella.
Roger, conozco muy bien RVO, && y la semántica de movimientos. Esta es una solución por ahora en una cantidad mínima de código y entidades. Además, todas las características de C ++ 0x no ayudarán con el objeto estático dentro del ejemplo de función, ya que no se nos permite definir funciones dentro de funciones.
Pavel Chikulaev