【问题标题】:Initializing a static std::map<int, int> in C++在 C++ 中初始化静态 std::map<int, int>
【发布时间】:2010-09-13 09:47:48
【问题描述】:

初始化静态地图的正确方法是什么?我们需要一个静态函数来初始化它吗?

【问题讨论】:

    标签: c++ stl stdmap


    【解决方案1】:

    使用 C++11:

    #include <map>
    using namespace std;
    
    map<int, char> m = {{1, 'a'}, {3, 'b'}, {5, 'c'}, {7, 'd'}};
    

    使用Boost.Assign

    #include <map>
    #include "boost/assign.hpp"
    using namespace std;
    using namespace boost::assign;
    
    map<int, char> m = map_list_of (1, 'a') (3, 'b') (5, 'c') (7, 'd');
    

    【讨论】:

    • 每次我看到用 C++ 完成的类似事情时,我都会想到它背后的所有可怕的模板代码。很好的例子!
    • 实现这些实用程序的所有可怕的模板代码的美妙之处在于它被整齐地封装在一个库中,最终用户很少需要处理复杂性。
    • @QBziZ:如果您的公司以 Boost 不够“标准”为由拒绝使用它,我想知道哪个 C++ 库“足够标准”。 Boost 是 C++ 编码器的标准伴侣。
    • 我对 Boost(这里和其他地方)的问题是你经常可以在没有它的情况下继续使用它(在这种情况下使用 C++11 或 C++11 之前的 with a function)。 Boost 增加了显着的编译时间开销,有大量文件要存放到您的存储库中(如果您正在制作存档,则必须复制/压缩/提取)。这就是我尽量不使用它的原因。我知道您可以选择包含/不包含哪些文件,但您通常不想担心 Boost 与自身的交叉依赖关系,因此您只需复制整个文件即可。
    • 我对 Boost 的问题是它经常有几个新的库依赖项,这通常意味着需要安装更多的包才能正常工作。我们已经需要 libstdc++。例如,Boost ASIO 库需要至少 2 个新库(可能更多)需要安装。 C++11/14 确实让不需要 Boost 变得容易得多。
    【解决方案2】:

    最好的方法是使用函数:

    #include <map>
    
    using namespace std;
    
    map<int,int> create_map()
    {
      map<int,int> m;
      m[1] = 2;
      m[3] = 4;
      m[5] = 6;
      return m;
    }
    
    map<int,int> m = create_map();
    

    【讨论】:

    • 为什么这是“最好的”?例如,为什么它比@Dreamer 的答案更好?
    • 我认为它是“最好的”,因为它非常简单并且不依赖于现有的其他结构(例如 Boost::Assign 或它的重新实现)。与@Dreamer 的回答相比,好吧,我避免创建一个仅用于初始化地图的整个结构......
    • Note there is a danger hereextern 变量在此“在主运行时构造函数之前”中将没有正确的值如果编译器只看到了 extern 声明,但还没有遇到实际的变量定义
    • 不,危险在于没有说明静态变量的初始化顺序(至少跨编译单元)。但这不是与这个问题相关的问题。这是静态变量的普遍问题。
    • 没有提升和没有 C++11 => +1。请注意,该函数可用于初始化 const map&lt;int,int&gt; m = create_map()(因此,在初始化列表中初始化类的 const 成员:struct MyClass {const map&lt;int, int&gt; m; MyClass(); }; MyClass::MyClass() : m(create_map())
    【解决方案3】:

    制作类似于boost的东西并不是一个复杂的问题。这是一个只有三个函数的类,包括构造函数,用于复制 boost 所做的(几乎)。

    template <typename T, typename U>
    class create_map
    {
    private:
        std::map<T, U> m_map;
    public:
        create_map(const T& key, const U& val)
        {
            m_map[key] = val;
        }
    
        create_map<T, U>& operator()(const T& key, const U& val)
        {
            m_map[key] = val;
            return *this;
        }
    
        operator std::map<T, U>()
        {
            return m_map;
        }
    };
    

    用法:

    std::map mymap = create_map(1,2)(3,4)(5,6);

    上面的代码最适合初始化全局变量或需要初始化的类的静态成员,您不知道何时首先使用它,但您想确保其中的值可用。

    如果说,您必须将元素插入到现有的 std::map 中...这是适合您的另一个类。

    template <typename MapType>
    class map_add_values {
    private:
        MapType mMap;
    public:
        typedef typename MapType::key_type KeyType;
        typedef typename MapType::mapped_type MappedType;
    
        map_add_values(const KeyType& key, const MappedType& val)
        {
            mMap[key] = val;
        }
    
        map_add_values& operator()(const KeyType& key, const MappedType& val) {
            mMap[key] = val;
            return *this;
        }
    
        void to (MapType& map) {
            map.insert(mMap.begin(), mMap.end());
        }
    };
    

    用法:

    typedef std::map<int, int> Int2IntMap;
    Int2IntMap testMap;
    map_add_values<Int2IntMap>(1,2)(3,4)(5,6).to(testMap);
    

    在此处查看 GCC 4.7.2 的实际效果:http://ideone.com/3uYJiH

    ############### 下面的所有内容都已过时#################

    编辑:下面的 map_add_values 类是我建议的原始解决方案,但在 GCC 4.5+ 时会失败。请查看上面的代码,了解如何向现有地图添加值。

    
    template<typename T, typename U>
    class map_add_values
    {
    private:
        std::map<T,U>& m_map;
    public:
        map_add_values(std::map<T, U>& _map):m_map(_map){}
        map_add_values& operator()(const T& _key, const U& _val)
        {
            m_map[key] = val;
            return *this;
        }
    };

    用法:

    std::map my_map;
    // 稍后在代码中的某个地方
    map_add_values(my_map)(1,2)(3,4)(5,6);

    注意:以前我使用operator [] 来添加实际值。正如 dalle 所说,这是不可能的。

    ##################### 过时部分结束################### ##

    【讨论】:

    • 我正在使用您的第一个示例作为 将错误编号(来自枚举)与消息绑定 - 它就像一个魅力 - 谢谢。
    • operator[] 只接受一个参数。
    • @dalle: 好收获!出于某种原因,我认为重载的 [] 运算符可以接受更多。
    • 这是一个绝妙的答案。很遗憾,OP从未选择过一个。你应该得到超级道具。
    • map_add_values 在 gcc 中不起作用,它抱怨:error: conflicting declaration ‘map_add_values&lt;int, int&gt; my_map’ error: ‘my_map’ has a previous declaration as ‘std::map&lt;int, int&gt; my_map’
    【解决方案4】:

    这是使用 2 元素数据构造函数的另一种方式。不需要任何函数来初始化它。没有第 3 方代码 (Boost),没有静态函数或对象,没有技巧,只有简单的 C++:

    #include <map>
    #include <string>
    
    typedef std::map<std::string, int> MyMap;
    
    const MyMap::value_type rawData[] = {
       MyMap::value_type("hello", 42),
       MyMap::value_type("world", 88),
    };
    const int numElems = sizeof rawData / sizeof rawData[0];
    MyMap myMap(rawData, rawData + numElems);
    

    自从我写了这个答案后,C++11 就出来了。您现在可以使用新的初始化列表功能直接初始化 STL 容器:

    const MyMap myMap = { {"hello", 42}, {"world", 88} };
    

    【讨论】:

      【解决方案5】:

      例如:

      const std::map<LogLevel, const char*> g_log_levels_dsc =
      {
          { LogLevel::Disabled, "[---]" },
          { LogLevel::Info,     "[inf]" },
          { LogLevel::Warning,  "[wrn]" },
          { LogLevel::Error,    "[err]" },
          { LogLevel::Debug,    "[dbg]" }
      };
      

      如果map是一个类的数据成员,可以直接在header中初始化,方法如下(C++17起):

      // Example
      
      template<>
      class StringConverter<CacheMode> final
      {
      public:
          static auto convert(CacheMode mode) -> const std::string&
          {
              // validate...
              return s_modes.at(mode);
          }
      
      private:
          static inline const std::map<CacheMode, std::string> s_modes =
              {
                  { CacheMode::All, "All" },
                  { CacheMode::Selective, "Selective" },
                  { CacheMode::None, "None" }
                  // etc
              };
      }; 
      

      【讨论】:

      • 在这个例子中,最好使用std::array
      • @prehistoricpenguin,为什么?
      • 出于性能原因,这个函数可能是一个热点,std::array 会比map 查找更快。
      • @prehistoricpenguin,你能提供一个 std::array 的例子吗?
      • 可能是为了 CPU 性能。 . .但是在不知道 LogLevel 的整数值的情况下,您会冒内存性能的风险。这个枚举在数组中会很糟糕。枚举 LogLevel { Disabled=-100, Info, Warning=500, Error, Debug=32768 };
      【解决方案6】:

      我会将地图包装在一个静态对象中,并将地图初始化代码放入该对象的构造函数中,这样您就可以确定地图是在初始化代码执行之前创建的。

      【讨论】:

      • 我支持你。它也快一点:)
      • 比什么快一点?带有初始化程序的全局静态?不,不是(记住 RVO)。
      • 不错的答案。如果我看到实际的示例代码,我会很高兴
      【解决方案7】:

      只是想分享一个纯 C++ 98 的解决方法:

      #include <map>
      
      std::map<std::string, std::string> aka;
      
      struct akaInit
      {
          akaInit()
          {
              aka[ "George" ] = "John";
              aka[ "Joe" ] = "Al";
              aka[ "Phil" ] = "Sue";
              aka[ "Smitty" ] = "Yando";
          }
      } AkaInit;
      

      【讨论】:

      • 这不适用于没有默认构造函数的对象,恕我直言,插入方法应该是首选
      【解决方案8】:

      你可以试试:

      std::map <int, int> mymap = 
      {
              std::pair <int, int> (1, 1),
              std::pair <int, int> (2, 2),
              std::pair <int, int> (2, 2)
      };
      

      【讨论】:

      • 在 C++11 之前,您不能使用具有非聚合类型的初始化列表,在这种情况下,您最好使用较短的语法 {1, 2} 而不是 std::pair&lt;int, int&gt;(1, 2)
      【解决方案9】:

      这类似于PierreBdR,没有复制地图。

      #include <map>
      
      using namespace std;
      
      bool create_map(map<int,int> &m)
      {
        m[1] = 2;
        m[3] = 4;
        m[5] = 6;
        return true;
      }
      
      static map<int,int> m;
      static bool _dummy = create_map (m);
      

      【讨论】:

      • 它可能无论如何都不会被复制。
      • 但是这样 map 不可能是静态常量,不是吗?
      【解决方案10】:

      如果你卡在 C++98 并且不想使用 boost,这里有我在需要初始化静态映射时使用的解决方案:

      typedef std::pair< int, char > elemPair_t;
      elemPair_t elemPairs[] = 
      {
          elemPair_t( 1, 'a'), 
          elemPair_t( 3, 'b' ), 
          elemPair_t( 5, 'c' ), 
          elemPair_t( 7, 'd' )
      };
      
      const std::map< int, char > myMap( &elemPairs[ 0 ], &elemPairs[ sizeof( elemPairs ) / sizeof( elemPairs[ 0 ] ) ] );
      

      【讨论】:

        【解决方案11】:

        除了使用的最佳答案

        const std::map<int, int> m = {{1,1},{4,2},{9,3},{16,4},{32,9}}
        

        通过直接调用在某些情况下可能有用的 lambda 还有另一种可能性:

        const std::map<int, int> m = []()->auto {
          std::map<int, int> m;
          m[1]=1;
          m[4]=2;
          m[9]=3;
          m[16]=4;
          m[32]=9;
          return m;
        }();
        

        显然,使用文字值从头开始编写时,一个简单的初始化列表会更好,但它确实开辟了其他可能性:

        const std::map<int, int> m = []()->auto {
          std::map<int, int> m;
          for(int i=1;i<5;++i) m[i*i]=i;
          m[32]=9;
          return m;
        }();
        

        (如果你想重复使用它,显然它应该是一个正常的函数;这确实需要最近的 C++。)

        【讨论】:

          【解决方案12】:

          你在这里有一些非常好的答案,但我对我来说,这看起来像是“当你所知道的只是一把锤子”......

          为什么没有初始化静态地图的标准方法的最简单答案是没有充分的理由使用静态地图...

          地图是为快速查找一组未知元素而设计的结构。如果您事先知道元素,只需使用 C 数组。以排序方式输入值,或者对它们运行排序,如果您不能这样做。然后,您可以通过使用 stl::functions 循环条目 lower_bound/upper_bound 来获得 log(n) 性能。当我之前对此进行测试时,它们的执行速度通常至少比地图快 4 倍。

          优点很多... - 更快的性能(*4,我在许多 CPU 类型上测量过,它总是在 4 左右) - 更简单的调试。更容易看到线性布局发生了什么。 - 如果有必要,复制操作的简单实现。 - 它在运行时不分配内存,因此永远不会抛出异常。 - 它是一个标准接口,因此很容易在 DLL 或语言等之间共享。

          我可以继续说下去,但如果你想了解更多,何不看看 Stroustrup 关于这个主题的许多博客。

          【讨论】:

          • 性能并不是使用地图的唯一原因。例如,在很多情况下,您希望将值链接在一起(例如,带有错误消息的错误代码),而映射使使用和访问相对简单。但是这些博客条目的链接可能很有趣,也许我做错了什么。
          • 如果你可以使用数组,它会更简单,性能也更高。但是,如果索引(键)不连续且间距很大,则需要一个映射。
          • map 也是表示部分函数(数学意义上的函数;但也有点像编程意义上的函数)的有用形式。数组不这样做。例如,您不能使用字符串从数组中查找数据。
          • 你的答案并没有试图回答有效的问题,而是推测语言的局限性,提出不同问题的解决方案,因此投反对票。一个真实的场景 - 将库错误代码映射(连续或不连续)到文本字符串。使用数组,搜索时间是O(n),可以通过静态映射到O(log(n))来改善。
          • 如果确实“没有充分的理由使用静态映射......”那么在 C++11 中添加使它们易于使用的语法(初始化列表)是非常奇怪的.
          猜你喜欢
          • 2015-05-08
          • 1970-01-01
          • 1970-01-01
          • 2021-01-31
          • 2015-09-05
          • 2021-03-19
          • 1970-01-01
          • 2012-02-01
          • 2011-01-11
          相关资源
          最近更新 更多