如何在C++中初始化一个私有的静态常量映射?

130

我只需要一个字典或关联数组,其键(key)为字符串(string),值(value)为整数(int)。

C++中有类型(map)适用于这种情况。

但我只需要一个用于所有实例的映射(-> 静态(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();

};

2
你所指的问题是什么?你是在尝试从另一个全局静态变量/常量中使用这个映射吗? - Péter Török
1
这不是一个关联数组 string => int,你正在将一个 int 映射到一个 char。v = k + 'a' - 1 - johnsyweb
11个回答

140
C++11标准引入了统一初始化,如果您的编译器支持的话,这将使得这个过程变得更简单:
//myClass.hpp
class myClass {
  private:
    static const map<int,int> myMap;
};

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

另请参阅《Professional C++》中的此部分,关于unordered_maps。

我们在cpp文件中是否需要等号? - phoad
@phoad:等号是多余的。 - Jinxed
谢谢您展示用法。这对于理解如何修改静态变量非常有帮助。 - User9102d82
非常适合基于第三方API创建const查找表,该API由键的#define常量组成,确保没有重复的键。 - nmz787
2
唯一的问题是它不是const。你可以在类中定义为static const,在cpp中定义为const map<...>,但要访问它,你需要使用at()而不是[ ] - Dmitry
1
@Dmitry 我刚刚添加了const关键字,这只是一个微不足道的操作;其他方面都不需要改变。 - undefined

121
#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编译时,这段代码可以通过,但是当我在main()函数中添加cout << A::myMap[1];后,就会出现错误。如果我移除const限定符,则不会出现错误,因此我猜测map的operator[]不能处理const map,至少在g++实现的C++库中是如此。 - Craig McQueen
2
错误是:const_map.cpp:22:23: 错误:将‘const std::map<int, int>’作为‘this’参数传递给‘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]’会丢弃限定符[-fpermissive] - Craig McQueen
7
实际上,地图的 operator[] 不能用于 const 地图,因为该运算符会在键不存在时创建引用的条目(因为它返回映射值的引用)。C++11 引入了 at(KeyValT key) 方法,让您能够访问具有给定键的项,如果不存在则抛出异常。 (http://en.cppreference.com/w/cpp/container/map/at) 此方法可用于 const 实例,但无法用于在非 const 实例中插入元素(就像 [] 运算符一样)。 - mbargiel
1
这很不错,但我暂时不点赞,因为该解决方案没有保持地图的私密性。有没有一种方法可以在不向子类或好管闲事的用户公开地图的情况下实现呢? - Benjamin

12

如果你觉得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();

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

7

如果地图中只包含在编译时已知的条目,并且地图的键是整数,则根本不需要使用地图。

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

6
感谢您指出地图不是必需的,但是您无法迭代这个。 - Viktor Sehr
4
那个“switch”太糟糕了,为什么不使用“return key + 'a' - 1”呢? - johnsyweb
12
@Johnsyweb。我认为原帖提供的映射仅作为示例,不代表他实际拥有的映射。因此,我也会假设return key + 'a' - 1对于他实际的映射是无效的。 - Matthew T. Staebler

5

一种不同的解决问题的方法:

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
}

这样更加高效,因为没有将堆栈中的内容(包括所有元素的构造函数和析构函数)复制到堆中。这是否重要取决于您的用例。对于字符串不重要!(但是您可能会发现此版本更“清晰”)

3
RVO 可以消除我和尼尔答案中的复制。 - Roger Pate

3
你可以尝试这个:

我的类.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

使用这种实现方式,您的类的常量静态映射是一个私有成员,可以通过公共获取方法访问其他类。否则,由于它是常量且不能更改,您可以删除公共获取方法并将映射变量移动到类的公共部分。但是,如果需要继承和/或多态性,则建议将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.

我已经编辑了原始帖子,原始代码没有问题,因为我发布的代码可以编译、构建和运行。只是我的第一个回答版本中,地图被声明为公共的常量,但不是静态的。


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

1
你可以使用单例模式来实现此功能。
// The static pointer is initialized exactly once which ensures that 
// there is exactly one copy of the map in the program, it will be 
// initialized prior to the first access, and it will not be destroyed 
// while the program is running.
class myClass {
  private:
  static std::map<int,int> myMap() {
    static const auto* myMap = new std::map<int,int> {
      {1, 2},
      {3, 4},
      {5, 6}
    };
    return *myMap;
  }
}

你可以像这样使用你的地图。
int x = myMap()[i] //where i is a key in the map

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
}

6
函数确实可以用来初始化一个常量对象。 - anon
在 OP 的代码中,static map<int,int> myMap = create_map(); 是不正确的。 - Prasoon Saurav
3
问题中的代码是错误的,我们都同意这一点,但这与你在这个答案中所说的“常量表达式”无关,而是因为只有当类的常量静态成员为整数或枚举类型时,才能在声明中初始化。对于所有其他类型,初始化必须在成员定义中进行,而不是在声明中进行。 - David Rodríguez - dribeas
VS6 不喜欢在这种声明中初始化 int 类型。 - Basilevs
1
@Prasoon:我不知道编译器会说什么,但问题代码中的错误是在类声明中初始化一个常量成员属性,无论初始化是否为常量表达式。如果您定义一个类:struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td; 即使使用常量表达式(5)执行初始化,它也将无法编译。也就是说,“常量表达式”与初始代码的正确性(或缺乏正确性)无关。 - David Rodríguez - dribeas
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接