Как инициализировать частную статическую константную карту в C ++?

108

Мне нужен только словарь или ассоциативный массив string=> int.

Для этого случая существует карта типов C ++.

Но мне нужна только одна карта для всех экземпляров (-> static), и эту карту нельзя изменить (-> const);

Я нашел этот способ с помощью библиотеки boost

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

Есть ли другое решение без этой библиотеки? Я пробовал что-то подобное, но всегда возникают проблемы с инициализацией карты.

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();

};
Мелун
источник
1
Какие проблемы вы имеете в виду? Вы пытаетесь использовать эту карту из другой глобальной статической переменной / константы?
Péter Török
Это не ассоциативный массив string => int, вы сопоставляете int с char. v = k + 'a' - 1.
Johnsyweb

Ответы:

108
#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() {
}

источник
3
+1 для простоты, конечно, использовать Boost.Assignподобный дизайн тоже довольно красиво :)
Matthieu M.
5
+1, спасибо. Примечание: мне пришлось поместить строку инициализации в свой файл реализации; оставив его в файле заголовка, я получил ошибки из-за нескольких определений (код инициализации запускался всякий раз, когда где-то был включен заголовок).
System.Cats.Lol
1
С g ++ v4.7.3 это компилируется, пока я не добавлю cout << A::myMap[1];в main(). Выдает ошибку. Ошибка не возникает, если я удаляю constквалификаторы, поэтому я полагаю, что map не operator[]может обрабатывать a const map, по крайней мере, не в реализации g ++ библиотеки C ++.
Craig McQueen
2
Ошибка: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
В самом деле, оператор operator [] карты не может работать с константной картой, потому что этот оператор создает указанную запись, если она не существует (поскольку он возвращает ссылку на сопоставленное значение). C ++ 11 представил метод at (KeyValT key), который позволяет вам получить доступ к элементу с заданным ключом, выбрасывая исключение, если он не существует. ( en.cppreference.com/w/cpp/container/map/at ) Этот метод будет работать с экземплярами констант, но его нельзя использовать для вставки элемента в неконстантный экземпляр (как и оператор []).
mbargiel
109

Стандарт C ++ 11 представил унифицированную инициализацию, которая делает это намного проще, если ваш компилятор поддерживает ее:

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


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

См. Также этот раздел из Professional C ++ о unordered_maps.

Дэвид С. Бишоп
источник
Нужен ли нам вообще знак равенства в файле cpp?
phoad
@phoad: Знак равенства излишний.
Jinxed
Спасибо, что показали использование. Было действительно полезно понять, как изменять статические переменные.
User9102d82 09
12

Я это сделал! :)

Прекрасно работает без 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
источник
11

Если вы сочтете boost::assign::map_list_ofполезным, но по какой-то причине не можете его использовать, вы можете написать свой собственный :

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';
}

Полезно знать, как такие вещи работают, особенно когда они такие короткие, но в этом случае я бы использовал функцию:

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();
Ю Хао
источник
6

Другой подход к проблеме:

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
}

Это более эффективно, поскольку нет однотипного копирования из стека в кучу (включая конструктор, деструкторы для всех элементов). Важно это или нет, зависит от вашего варианта использования. Со струнами все равно! (но вы можете найти эту версию «чище», а можете и не найти)

ypnos
источник
3
RVO исключает копирование в моем ответе и ответе Нила.
6

Если карта должна содержать только записи, которые известны во время компиляции, а ключи карты являются целыми числами, то вам вообще не нужно использовать карту.

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
    }
}
Мэтью Т. Стэблер
источник
5
+1 за указание на то, что карта не нужна, однако вы не можете повторять это
Виктор Сер
4
Это switchужасно, хотя. А почему бы и нет return key + 'a' - 1?
Johnsyweb
12
@Johnsyweb. Я предполагаю, что отображение, предоставленное исходным плакатом, было представлено исключительно в качестве примера и не указывает на фактическое отображение, которое у него есть. Поэтому я также полагаю, что return key + 'a' - 1это не сработает для его реального отображения.
Мэтью Т. Стэблер,
3

Вы можете попробовать это:

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

С этой реализацией постоянная статическая карта ваших классов является закрытым членом и может быть доступна другим классам с помощью общедоступного метода get. В противном случае, поскольку он постоянен и не может быть изменен, вы можете удалить общедоступный метод get и переместить переменную карты в публичный раздел классов. Однако я бы оставил метод createMap закрытым или защищенным, если требуется наследование и / или полиморфизм. Вот несколько примеров использования.

 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.

Я отредактировал свой исходный пост, в исходном коде, который я опубликовал, не было ничего плохого, он был скомпилирован, построен и запущен правильно, просто моя первая версия, которую я представил в качестве ответа, была объявлена ​​как общедоступная, а карта была const, но не статический.

Фрэнсис Куглер
источник
2

Если вы используете компилятор, который по-прежнему не поддерживает универсальную инициализацию, или у вас есть резервирование использования Boost, другой возможной альтернативой будет следующая

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;
}();
Абхиджит
источник
0

Вызов функции не может появляться в постоянном выражении.

попробуйте это: (просто пример)

#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
источник
6
Безусловно, функцию можно использовать для инициализации константного объекта.
В коде OP static map<int,int> myMap = create_map();неверный.
Prasoon Saurav
3
Код в вопросе неверен, мы все согласны с этим, но он не имеет ничего общего с «постоянными выражениями», как вы говорите в этом ответе, а скорее с тем фактом, что вы можете инициализировать только постоянные статические члены класса в объявление, если они имеют целочисленный или перечисляемый тип. Для всех других типов инициализация должна выполняться в определении члена, а не в объявлении.
Дэвид Родригес - дрибес,
Ответ Нила компилируется с помощью g ++. Тем не менее, я помню, что у меня были некоторые проблемы с этим подходом в более ранних версиях инструментальной цепочки GNU. Есть ли универсальный правильный ответ?
Basilevs
1
@Prasoon: я не знаю, что говорит компилятор, но ошибка в коде вопроса инициализирует постоянный атрибут-член типа класса в объявлении класса, независимо от того, является ли инициализация постоянным выражением или нет. Если вы определяете класс: struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;он не будет скомпилирован, даже если инициализация выполняется с постоянным выражением ( 5). То есть «постоянное выражение» не имеет отношения к правильности (или ее отсутствию) исходного кода.
Дэвид Родригес - дрибес,
-2

Я часто использую этот паттерн и рекомендую вам тоже его использовать:

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;

Конечно, это не очень удобно для чтения, но без других библиотек это лучшее, что мы можем сделать. Также не будет никаких лишних операций, таких как копирование с одной карты на другую, как в вашей попытке.

Это даже более полезно внутри функций: Вместо:

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

Используйте следующее:

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

Мало того, что вам больше не нужно здесь иметь дело с логической переменной, у вас не будет скрытой глобальной переменной, которая проверяется, если инициализатор статической переменной внутри функции уже был вызван.

Павел Чикулаев
источник
6
Наследование должно быть крайним средством, а не первым.
Компилятор, поддерживающий RVO, исключает избыточное копирование с версиями функций. Семантика перемещения C ++ 0x устраняет остальное, как только они становятся доступными. В любом случае, я сомневаюсь, что это близко к тому, чтобы стать узким местом.
Роджер, я хорошо знаю семантику RVO, && и move. На данный момент это решение с минимальным количеством кода и сущностей. Кроме того, все функции C ++ 0x не помогут со статическим объектом внутри примера функции, поскольку нам не разрешено определять функции внутри функций.
Павел Чикулаев