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

42

如果我编写这段代码

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 ‘{...}’   

有些让我烦恼的是构造函数是在运行时执行的,理论上可能会失败。

当然,如果失败了,它会快速失败,并且应该始终如一地这样做,以便我能够快速定位和纠正问题。

但是,我还是很好奇 - 有没有办法在编译时初始化 map、vector 等?


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

至于 Boost,我还没决定。他们对在嵌入式系统中使用他们的有限状态机类并不确定,而这正是我在这里编码的,事件/状态/Fsm 类。

唉,我想我最好还是保险点,但我希望这次讨论对其他人有所帮助。


1
请查看http://www.state-machine.com/以获取嵌入式SM库。 - rpg
谢谢,我知道这个(但还是+1,因为它可能会帮助其他人)。对我来说似乎有点过于复杂,但我确实需要操作系统抽象层,所以...也许...也许这只是“非自己发明”的综合症;-) - Mawg says reinstate Monica
1
这里是一个类似的Stack Overflow问题的答案,它巧妙地使用了模板类和运算符重载。 - wip
哇,gcc在他们的错误信息上真的做得很好。 - Tomáš Zato
1
在c++11(clang)中,我得到了上述语法的错误,将括号替换为大括号可以解决它。 - Jehandad
9个回答

38

虽然不是完全的静态初始化,但还是可以尝试一下。 如果你的编译器不支持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]);

这段代码非常易读,不需要额外的库,并且应该在所有编译器中都可以使用。


8
请注意,这种方法将为您的map_data分配内存来容纳每个临时std::pair项目,将静态声明的字符串复制到pair::second中,然后再次复制文本到std::map中,最后销毁临时对象。对于大量静态声明的项来说,这将非常浪费。 - Armentage

22

在C++98中不支持此功能。C++11支持此功能,因此如果启用C++11标志并包含g++建议的内容,则可以使用。

编辑:从gcc 5开始,默认情况下启用了C++11。


1
是的,这个功能已经缺失很久了。我真的希望C++0x会成为C++OA,这样我们就可以开始向编译器供应商要求这个功能。 - daramarak
1
嗯,如果我理解正确的话,如果语义没有改变,实现也可能静态初始化非聚合体。如果静态分析不能保证这一点,C++0x初始化列表也不会改变任何事情。 - Georg Fritzsche
5
我认为语法应该像这样: { {1,'a'} }; - Vivek Goel
@StudentT - 我不确定VS 2012是否支持,但是它在VS 2015上支持。示例中的语法有误,请参阅Vivek Goel在您之前的评论。 - Class Skeleton
1
我敢打赌,今天没有任何编译器能将其编译为静态初始化。对于std::map而言是不可能的。也许在C++17中,可以实现一个constexpr映射表/哈希表--但很可能不能使用std::map接口。 - Paul Groke
显示剩余5条评论

14
你可以使用`Boost.Assign`库:

你可以使用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和其他人在下面的评论中指出的那样,这种初始化发生在运行时,类似于UncleBean的建议。


2
不是的,boost::assign 会导致运行时初始化。 - Georg Fritzsche
@Neil,@gf 噢,明白了,我错过了重点,它肯定不是运行时。 - mloskot

14

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

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

那些用于构造pair的括号没有意义。作为替代,您可以完全命名每个pair或使用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元编程这样的库来创建编译时映射(不确定是否完全正确,并且没有研究过它可能有什么用途):

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,你的第二个例子无法编译,我认为这种方法必须按照我在答案中发布的方式进行。 - Dmitry
@Dmitry:第二个示例也假定了 -std=C++0x(OP 的编译器似乎支持它,否则就不会谈论 initializer_list 了)。 - UncleBens
UncleBens,哦,好的,我不知道,我的gcc有点老,所以我还不能使用C++0x。 - Dmitry

4

在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)的静态初始化,但前提是与动态初始化相比不会改变语义。
因此,在我看来,只有在您的实现可以通过静态分析验证行为不会更改的情况下,才能获得所请求的内容,但无法保证发生。


1

有一个技巧可以使用,但前提是此数据将不会在任何其他静态构造函数中使用。 首先定义一个简单的类,如下所示:

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


当然,这有点过度杀伤,所以我建议只在确实需要时使用它。


9
双下划线被编译器保留。 - GManNickG
1
我认为这只是C++0x和boost.assign建议的另一种变体。这里没有编译时内容,只是在全局映射实例中设置值的另一种方式。 - UncleBens
我用下划线只是为了清楚地表明您不想在剩余的代码中触碰函数或对象。(你也可以使用未命名的命名空间) 是的,这与编译时无关,但正如我所说,这是一个技巧——数据将在main()之前初始化。 - Adis H

1

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

请记住,STL只是一个接口规范。您可以创建自己的符合规范的容器,并赋予它们静态初始化能力。

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


任何 std::map 的实现都不可能同时支持静态初始化和符合标准。swap() 迭代器有效性要求使得使用动态存储对于 std::map 和除了 std::array 之外的所有标准容器来说都是绝对必要的。 - Ben Voigt
@BenVoigt 这个答案似乎基于对大括号和静态性之间联系的误解。然而,只要std::allocator没有被专门化,编译器就可以将const map<T, const U, std::allocator<pair<T, const U>>>节点放在ROM中。如果编译器能够检测到节点释放量等于一个平凡析构函数和一个free,并且它知道free忽略ROM地址,则可以跳过malloc并静态地分配ROM。尽管如此,肯定没有编译器这样做过。(这个问题不那么“const”,但原则是相同的。) - Potatoswatter

0
不,因为你的例子中,地图、向量和字符串的所有组件都是通过new在运行时分配内存来构造的。使用std::initializer_list和其他语法糖最终只是在我们的代表上做构造函数的工作。
要使其真正成为编译时,就像你在obj中的结构一样,你必须部分编译程序,运行它,将初始值的结果序列化到obj中,然后在加载时恢复该过程。对于简单的数组、字符串和标量来说,这样做并不太困难,但对于一整套复杂的结构来说就另当别论了。
但是,先暂停一下序列化的思路。你可以做的是,像使用nlohmann json这样的库来序列化/反序列化你的结构,然后将数据结构的json字符串作为一个字符串常量。这些数据在编译时会很好地保存,然后你可以在运行时从中加载。或者,你可以继续使用现有的方法,拼凑一组已知的编译时构造,并在运行时从中读取。例如,你可以将地图表示为一个2xN的数组,然后在运行时读取该数组。

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

5
那不是一张真正的地图,而且肯定不是问题中提到的std::map。 - Kleist

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