25

我最近偶然发现了初始化列表的一些问题。考虑一个存储类似地图的数据的程序

struct MyMapLike {
  MyMapLike(std::map<std::string, int> data)
    :data(std::move(data))
  { }

private:
  std::map<std::string, int> data;
};

这看起来直截了当。但是当初始化它时,它变得丑陋。我想让它看起来像

MyMapLike maps = { { "One", 1 }, { "Two", 2 } };

但是编译器并不想接受这个,因为上面的意思是它应该寻找一个可以分别接受{ "One", 1 }和的双参数构造函数{ "Two", 2 }。我需要添加额外的大括号,使其看起来像一个接受的单参数构造函数{ { ... }, { ... } }

MyMapLike maps = { { { "One", 1 }, { "Two", 2 } } };

我不想那样写。由于我有一个类似映射的类,并且初始化程序具有映射列表的抽象值,因此我想使用以前的版本,并且独立于任何此类实现细节,例如构造函数的嵌套级别。

一种解决方法是声明一个初始化列表构造函数

struct MyMapLike {
  MyMapLike(std::initializer_list< 
    std::map<std::string, int>::value_type
    > vals)
    :data(vals.begin(), vals.end())
  { }

  MyMapLike(std::map<std::string, int> data)
    :data(std::move(data))
  { }

private:
  std::map<std::string, int> data;
};

现在我可以使用前者,因为当我有一个初始化列表构造函数时,整个初始化列表被视为一个元素,而不是被拆分为多个元素。但我认为构造函数的这种单独需求非常丑陋。

我正在寻找指导:

  • 您如何看待前一种和后一种形式的初始化?在这种情况下需要额外的大括号是否有意义?
  • 您认为在这种情况下添加初始化列表构造函数的要求很糟糕吗?

如果你同意我的观点,前一种初始化方式更好,你能想到什么解决方案?

4

6 回答 6

10

由于我有一个类似地图的类,并且初始化程序具有映射列表的抽象值,我想使用以前的版本

这就是问题所在:由提供允许您的类被视为地图的构造函数。你称你的解决方案是一种变通方法,但没有什么可以变通的。:)

但我认为构造函数的这种单独需求非常丑陋。

它是,但不幸的是,因为它是你的类,你必须指定初始化列表的工作方式。

于 2011-04-21T15:51:48.630 回答
6

您如何看待前一种和后一种形式的初始化?在这种情况下需要额外的大括号是否有意义?

我认同。我认为它允许太多的歧义允许构造函数不仅在语法匹配该构造函数时被调用,而且当语法与构造函数的单个参数的某些构造函数匹配时,等等递归。

struct A { int i; };
struct B { B(A) {}  B(int) {} };
struct C { C(B) {} };

C c{1};

您认为在这种情况下添加初始化列表构造函数的要求很糟糕吗?

不。它可以让你得到你想要的语法,但不会产生如果我们让编译器更难搜索构造函数时出现的问题。

于 2012-04-02T22:31:31.267 回答
4

这样的东西不会产生预期的效果(MyMapLike可以以任何可以的方式构造std::map,但不会隐式转换为std::map)?

struct MyMapLike : private std::map<std::string, int>
{
    using map::map;
};

如果它绝对肯定必须是成员,则可以使用构造函数完美转发(我不确定确切的语法),如下所示:

struct MyMapLike
{
    template<typename... Initializers>
    MyMapLike(Initializers... init, decltype(new std::map<std::string, int>(...init)) = 0)
      : data(...init)
    { }

private:
    std::map<std::string, int> data;
};
于 2011-04-20T20:33:43.327 回答
1

您可以按如下方式进行初始化:

MyMapLike maps ( { { "One", 1 }, { "Two", 2 } } );

外部( ... )现在显然只是简单地围绕构造函数参数,并且更容易看出最外部{ ... }定义了“列表”。

它仍然有点冗长,但它避免了{用于做两件不同事情的情况,这会影响可读性。

于 2013-12-21T20:14:37.200 回答
0

我同意这很丑。一旦我超越了非常简单的情况,我通常会废弃初始化列表,因为它们非常有限。对于您的情况,我会选择 boost::assign,虽然它不那么简洁,但可以提供更好的灵活性和控制力。

于 2011-04-20T17:09:58.083 回答
0

您如何看待前一种和后一种形式的初始化?在这种情况下需要额外的大括号是否有意义?

我更喜欢第一种形式,因为它有一个干净的类接口。采用初始化列表的构造函数会污染接口并且提供很少的回报。

额外的大括号是我们会习惯的。就像我们习惯了 C++ 的其他怪癖一样。

于 2011-04-21T17:31:23.643 回答