C++,我可以在编译时静态初始化 std::map 吗?

Posted

技术标签:

【中文标题】C++,我可以在编译时静态初始化 std::map 吗?【英文标题】:C++, can I statically initialize a std::map at compile time? 【发布时间】:2011-01-11 10:31:18 【问题描述】:

如果我编码这个

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

然后g++对我说

deducing from brace-enclosed initializer list requires #include <initializer_list>
in C++98 ‘example’ must be initialized by constructor, not by ‘...’   

这让我有点恼火,因为构造函数是运行时的,理论上可能会失败。

当然,如果确实如此,它会很快失败,并且应该始终如一地这样做,因此我应该快速定位并纠正问题。

但是,我仍然很好奇 - 是否有在编译时初始化地图、矢量等?


编辑:我应该说我正在为嵌入式系统开发。并非所有处理器都有 C++0x 编译器。最受欢迎的可能会,但我不想遇到问题并且必须维护 2 个版本的代码。

至于 Boost,我还没有决定。他们对在嵌入式系统中使用他们的有限状态机类持怀疑态度,所以这实际上是我在这里编码的,事件/状态/Fsm 类。

唉,我想我最好还是谨慎行事,但我希望这次讨论对其他人有所帮助。

【问题讨论】:

查看 state-machine.com 以获取嵌入式 SM 库。 谢谢,我知道(但仍然 +1,因为它可能会帮助其他人)。这对我来说似乎有点太多了,但我确实也需要 o/s 抽象,所以......也许......也许这只是 Not-Invented-Here 综合症 ;-) 这里是answer to a similar stack overflow question,它巧妙地使用了模板类和运算符重载。 哇,gcc 确实处理了他们的错误消息。 在 c++11 (clang) 中,上述语法出现错误,用大括号替换括号已修复。 【参考方案1】:

这并不完全是静态初始化,但仍然可以尝试一下。 如果你的编译器不支持 C++0x,我会选择 std::map 的迭代构造函数

std::pair<int, std::string> map_data[] = 
    std::make_pair(1, "a"),
    std::make_pair(2, "b"),
    std::make_pair(3, "c")
;

std::map<int, std::string> my_map(map_data,
    map_data + sizeof map_data / sizeof map_data[0]);

这非常易读,不需要任何额外的库,并且应该适用于所有编译器。

【讨论】:

请注意,此方法将构造分配内存以保存 map_data 中的每个临时 std::pair 项,将静态声明的字符串复制到 pair::second,然后再次将文本复制到 std ::map,最后破坏临时工。对于大量静态声明的项目,这将是相当浪费的。【参考方案2】:

在 C++98 中没有。 C++11 支持这一点,所以如果你启用 C++11 标志并包含 g++ 建议的内容,你可以。

编辑:从 gcc 5 C++11 默认开启

【讨论】:

是的,这是很长时间以来一直缺少的功能。我真的希望 C++0x 最终成为 C++OA,这样我们就可以开始向我们的编译器供应商提出要求。 嗯,如果我没看错的话,如果语义没有改变,实现可能也会静态初始化非聚合。如果静态分析不能保证,C++0x 初始化列表不会改变任何事情。 @daramarak:我认为编译器供应商是编写标准的人 ;v) ,无论您是否要求,都应该在主要编译器规范之后不久提供支持,对于小型编译器,很好运气。 我认为语法应该是这样的 1, 'a' ; 我敢打赌,到目前为止,没有编译器会将其编译为静态初始化。不适用于std::map。也许使用 C++17 可以实现 constexpr 映射/哈希表 - 但可能无法使用 std::map 接口。【参考方案3】:

你可以使用Boost.Assign库:

#include <boost/assign.hpp>
#include <map>
int main()

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

然而,正如 Neil 和其他人在下面的 cmets 中指出的那样,这种初始化发生在运行时,类似于 UncleBean 的提议。

【讨论】:

不是,boost::assign 导致运行时初始化。 @Neil,@gf 啊,明白了,我错过了它当然不能是运行时的要点。【参考方案4】:

对于 C++0x,您可能需要一直使用大括号(也为每一对使用新式语法):

std::map<int, char> example =  1,'a', 2, 'b', 3, 'c' ;

那些构造对的括号没有意义。或者,您可以完全命名每一对或使用 make_pair(就像在 C++98 中所做的那样)

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

关于在编译时创建这些实例:不。 STL 容器都完全封装了运行时内存管理。

我想,你真的只有一个编译时映射,其中包含诸如 boost 的元编程之类的库(不是 100% 肯定,如果它完全正确,并且还没有研究它可能有什么用处):

using namespace boost::mpl;
map<
    pair<integral_c<int, 1>, integral_c<char, 'a'> >,
    pair<integral_c<int, 2>, integral_c<char, 'b'> >,
    pair<integral_c<int, 3>, integral_c<char, 'c'> >
> compile_time_map;

【讨论】:

UncleBens,您的第二个示例无法编译,这种方法 IMO 必须 像我在答案中发布的那样完成。 @Dmitry:第二个示例也假设 -std=C++0x(OP 的编译器似乎支持它,否则它不会谈论 initializer_list)。 UncleBens,哦,好吧,不知道,我的 gcc 有点老了,我还没有 C++0x。【参考方案5】:

对于 C++0x 之前的版本,最接近的方法是不使用为运行时使用而设计的容器(并将自己限制在基本类型和聚合中)

struct pair  int first; char second; ;
pair arr[] = 1,'a', 2,'b'; // assuming arr has static storage

然后可以使用某种地图视图来访问它,或者您可以实现一个允许聚合初始化的包装器,类似于Boost.Array 所做的。

当然,问题是这些好处是否证明投入时间来实施这一点是合理的。

如果我在这里的阅读是正确的,C++0x 初始化器列表可能为您提供非聚合的静态初始化,例如 std::mapstd::pair,但前提是这不会改变语义与动态初始化相比。 因此,在我看来如果你的实现可以通过静态分析验证如果 map 被静态初始化,行为不会改变,你只能得到你所要求的,但不能保证它发生。

【讨论】:

【参考方案6】:

您可以使用一个技巧,但前提是此数据不会在任何其他静态构造函数中使用。 首先定义一个像这样的简单类:

typedef void (*VoidFunc)();
class Initializer

  public:
    Initializer(const VoidFunc& pF)
    
      pF();
    
;

然后,像这样使用它:

std::map<std::string, int> numbers;
void __initNumsFunc()

  numbers["one"] = 1;
  numbers["two"] = 2;
  numbers["three"] = 3;

Initializer __initNums(&__initNumsFunc);

当然,这有点矫枉过正,所以我建议您仅在确实需要时才使用它。

【讨论】:

双下划线为编译器保留。 我认为这只是 C++0x 和 boost.assign 建议的另一种变体。这里没有编译时,只是在全局地图实例中设置值的另一种方式。 我使用下划线只是为了表明您不想在剩余代码中触及函数或对象。 (你也可以使用未命名的命名空间)是的,没有任何关于它的编译时,但正如我所说,这是一个技巧 - 数据将在 main() 之前初始化【参考方案7】:

没有在编译时初始化std::map 的标准方法。正如其他人所提到的,C++0x 将允许编译器尽可能将初始化优化为静态,但这永远无法保证。

但请记住,STL 只是一个接口规范。您可以创建自己的兼容容器并为其提供静态初始化功能。

根据您是否计划升级编译器和 STL 实现(尤其是在嵌入式平台上),您甚至可以深入研究您正在使用的实现,添加派生类并使用它们!

【讨论】:

std::map 的任何实现都不可能兼容并支持静态初始化。 swap() 迭代器有效性要求绝对有必要使用动态存储,用于std::map 和除std::array 之外的所有其他标准容器。 @BenVoigt 这个答案似乎是基于对大括号和静态性之间连接的误解。但是,编译器可以将const map&lt;T, const U, std::allocator&lt;pair&lt;T, const U&gt;&gt;&gt; 节点放入ROM,只要std::allocator 不是专用的。如果编译器可以检测到节点释放相当于一个微不足道的析构函数和一个free,并且它知道free 忽略了ROM 地址,那么它可以跳过malloc 并静态分配ROM。也就是说,当然没有编译器这样做过。 (这个Q少了const,但原理是一样的。)【参考方案8】:
template <const int N> struct Map   enum  value = N; ;
template <> struct Map <1>  enum  value = (int)'a'; ;
template <> struct Map <2>  enum  value = (int)'b'; ;
template <> struct Map <3>  enum  value = (int)'c'; ;

std::cout  << Map<1>::value ;

【讨论】:

那不是真正的地图,也绝对不是问题所在的 std::map。

以上是关于C++,我可以在编译时静态初始化 std::map 吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 自定义分配器

在字符串的 std::map 中查找编译错误,长

接受对抽象类的 const 引用的 C++ 构造函数无法初始化 std::map

将 SWIG 与 C++ 的 std::map 一起使用时,Java 没有迭代器

C++ std::unordered_map怎么用

C# 与 C++ 静态数组中静态常量列表初始化的效率