复制函数对象和初始化列表

4
我对复制函数对象和/或初始化程序的操作机制有些困惑。在下面的代码中,我认为我会一直复制/移动对象,但无论如何它都会发生段错误。我似乎做错了什么,但还没有搞清楚我的错误假设是什么。 奇怪的是,在cppreference.com上,我找不到initializer_list的复制或移动构造函数,所以我想知道在这些情况下实际发生了什么。
#include <string>
#include <vector>
#include <functional>
#include <iostream>

std::initializer_list<std::function<std::string()>> getInitializer() {
  return {
    []() -> std::string {
      return "If";
    }
  };
}

int main() {
    std::function<int(std::string)> func;
    {
        auto init = getInitializer();

        func = [init](std::string text) -> int {
            std::vector<std::function<std::string()>> vec(init);

            for( auto& el : vec ) {
                std::cout << el();
            }
            std::cout << text << std::endl;
            return 5;
        };
    }

    return func(" you see this - the world is all right!");
}

为什么不在顶部添加一个 using 语句呢? - Cole Tobin
@ColeJohnson using 有什么用? - abergmeier
使用 using namespace std; 可以避免每次都要输入 std::。详情请参考:http://msdn.microsoft.com/zh-cn/library/aewtdfs3(v=vs.100).aspx - Cole Tobin
4
在任何地方都使用“using namespace std”不是一个好的做法,这会使得全局命名空间中存在许多你不会使用的符号而导致污染。@LCID 很好的问题,看起来原始的std::functions在某个地方被销毁了。 - mfontanini
1个回答

7
我对initializer_list没有太多经验,但标准似乎建议实现initializer_list时,将其视为指向数组的一对指针。在getInitializer中的列表具有自动生存期,支持它的数组也是如此。您最终会返回一对指向不存在的数组的指针。

标准的相关部分是8.5.4 [decl.init.list] 中的第5和第6项:

5.- 从初始化列表构造类型为std::initializer_list<E>的对象,就好像实现分配了一个元素类型为EN个元素的数组,其中N是初始化列表中的元素数。该数组的每个元素都与初始化列表的相应元素进行复制初始化,并构造std::initializer_list<E>对象以引用该数组。如果需要缩小转换来初始化任何元素,则程序无效。

6.- 数组的生存期与initializer_list对象的生存期相同。


因此,对于您的特定情况,实现大致等同于:
std::initializer_list<std::function<std::string()>> getInitializer() {
  std::function<std::string()> __a[1] = {
    []() -> std::string {
      return "If";
    }
  };
  return std::initializer_list<std::function<std::string()>>(__a, __a+1);
}

我有些困惑。为什么他们决定使用内存数组来实现std::initializer_list?为什么不基于std::array呢?对我来说,这个类完全失效了,因为普通容器的用法无法奏效。 - abergmeier
std::array 并不是问题所在(毕竟实现可以自由选择使用它)。标准委员会选择限制被引用对象的生命周期,以使 std::initializer_list 轻量且易于复制。它实际上具有引用语义,这确实是 C++ 中不寻常的。std::initializer_list 不是一个容器,你应该返回一个容器。 - Luc Danton
@LCID 火:为什么你不直接使用std::array呢? - K-ballo
@K-ballo 当你看到有多少容器在函数调用中接受 std::initializer_list 参数时,你就会有灵感 :). 最终我现在使用 std::vector - abergmeier
1
@LCID Fire:所有这些函数也有接受 [begin,end) 迭代器对的重载版本,这是在没有 std::initializer_list 的时代所采用的方式。 - K-ballo
@LCIDFire std::initializer_list 不是一个容器,它并不是因为设计上的问题而出现了故障。这里的问题在于你假设并使用它来做一些它从未被设计用于的事情。它是一个神奇的特性,可以方便地初始化容器,但不要试图无限期地存储它,将其用作类的成员,或将其视为一种通用的一等对象,适用于任何类型的代码。 - Jonathan Wakely

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