【问题标题】:How to initialize a private static const map in C++?如何在 C++ 中初始化私有静态 const 映射?
【发布时间】:2011-02-07 20:12:33
【问题描述】:

我只需要字典或关联数组string => int

这种情况下有类型映射 C++。

但我只需要一个映射用于所有实例(-> 静态),并且此映射不能更改(-> 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();

};

【问题讨论】:

  • 你指的是什么问题?您是否尝试从另一个全局静态变量/常量中使用此映射?
  • 这不是关联数组字符串 => int,您将 int 映射到 char。 v = k + 'a' - 1.

标签: c++ static map initialization constants


【解决方案1】:

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

另见this section from Professional C++,在 unordered_maps 上。

【讨论】:

  • cpp文件中是否需要等号?
  • @phoad:等号是多余的。
  • 感谢您展示用法。了解如何修改静态变量真的很有帮助。
  • 非常适合基于由 #define 密钥常量组成的第三方 API 制作 const 查找表,确保没有重复的密钥
  • 唯一的问题是不是const。您可以在类中将其定义为static const,在cpp 中定义为const map&lt;...&gt;,但要访问它,您需要使用at() 而不是[ ]
【解决方案2】:
#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() {
}

【讨论】:

  • +1 为简单起见,当然使用Boost.Assign 之类的设计也很简洁:)
  • +1,谢谢。注意:我必须将初始化行放在我的实现文件中;由于多个定义,将其留在头文件中会给我带来错误(只要在某处包含头文件,初始化代码就会运行)。
  • 使用 g++ v4.7.3 编译,直到我将 cout &lt;&lt; A::myMap[1]; 添加到 main() 中。它给出了一个错误。如果我删除const 限定符就不会发生错误,所以我猜map 的operator[] 不能处理const map,至少在C++ 库的g++ 实现中没有。
  • 错误是:const_map.cpp:22:23: error: passing ‘const std::map&lt;int, int&gt;’ as ‘this’ argument of ‘std::map&lt;_Key, _Tp, _Compare, _Alloc&gt;::mapped_type&amp; std::map&lt;_Key, _Tp, _Compare, _Alloc&gt;::operator[](const key_type&amp;) [with _Key = int; _Tp = int; _Compare = std::less&lt;int&gt;; _Alloc = std::allocator&lt;std::pair&lt;const int, int&gt; &gt;; std::map&lt;_Key, _Tp, _Compare, _Alloc&gt;::mapped_type = int; std::map&lt;_Key, _Tp, _Compare, _Alloc&gt;::key_type = int]’ discards qualifiers [-fpermissive]
  • 确实,映射的 operator[] 不能对 const 映射进行操作,因为如果引用的条目不存在(因为它返回对映射值的引用),该运算符会创建它。 C++11 引入了 at(KeyValT key) 方法,该方法允许您使用给定键访问项目,如果它不存在则抛出异常。 (en.cppreference.com/w/cpp/container/map/at) 此方法适用于 const 实例,但不能用于在非常量实例上插入元素(与 [] 运算符一样)。
【解决方案3】:

如果您发现boost::assign::map_list_of 有用,但由于某种原因无法使用,您可以write your own

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      如果映射只包含编译时已知的条目并且映射的键是整数,那么您根本不需要使用映射。

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

      【讨论】:

      • +1 用于指出不需要地图,但是,您不能对其进行迭代
      • 不过,switch 很糟糕。为什么不return key + 'a' - 1
      • @Johnsyweb。我假设原始海报提供的映射仅作为示例提供,并不表示他拥有的实际映射。因此,我还假设return key + 'a' - 1 不适用于他的实际映射。
      【解决方案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
      }
      

      这更有效,因为没有从堆栈到堆的单一类型副本(包括所有元素的构造函数和析构函数)。这是否重要取决于您的用例。与字符串无关! (但你可能会也可能不会发现这个版本“更干净”)

      【讨论】:

      • RVO 消除了我和 Neil 的回答中的复制。
      【解决方案7】:

      你可以试试这个:

      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 但不是静态的。

      【讨论】:

        【解决方案8】:

        如果您使用的编译器仍然不支持通用初始化,或者您对使用 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;
        }();
        

        【讨论】:

          【解决方案9】:

          函数调用不能出现在常量表达式中。

          试试这个:(只是一个例子)

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

          【讨论】:

          • 一个函数当然可以用来初始化一个const对象。
          • 在 OP 的代码中 static map&lt;int,int&gt; myMap = create_map(); 不正确。
          • 问题中的代码是错误的,我们都同意这一点,但这与您在此答案中所说的“常量表达式”无关,而是与您只能初始化的事实有关声明中类的常量静态成员,如果它们是整数或枚举类型。对于所有其他类型,初始化必须在成员定义中完成,而不是在声明中。
          • Neil 的答案是用 g++ 编译的。不过,我记得在早期版本的 GNU 工具链中这种方法存在一些问题。有没有普遍正确的答案?
          • @Prasoon:不知道编译器怎么说,但是问题代码中的错误是在类声明中初始化一个类类型的常量成员属性,不管初始化是否为常量表达与否。如果您定义一个类:struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;,即使使用常量表达式 (5) 执行初始化,它也将无法编译。也就是说,“常量表达式”与初始代码的正确性(或缺失)无关。
          【解决方案10】:

          您可以为此使用singleton pattern

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

          【讨论】:

            【解决方案11】:

            我经常使用这种模式,建议你也使用它:

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

            您不仅不再需要在这里处理布尔变量,而且如果已经调用了函数内部静态变量的初始化程序,您将不会检查隐藏的全局变量。

            【讨论】:

            • 继承应该是最后的手段,而不是第一个。
            • 支持 RVO 的编译器消除了函数版本的冗余复制。一旦可用,C++0x 移动语义就会消除其余部分。无论如何,我怀疑它即将成为瓶颈。
            • 罗杰,我非常了解 RVO、&& 和移动语义。这是目前使用最少代码和实体的解决方案。此外,所有 C++0x 特性都对函数内部的静态对象没有帮助,因为我们不允许在函数内部定义函数。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-12-31
            • 1970-01-01
            • 2011-04-01
            • 1970-01-01
            • 1970-01-01
            • 2012-12-15
            • 1970-01-01
            相关资源
            最近更新 更多