我可以保留 std::initializer_list<T>::begin() 返回的地址吗?

Can I hold onto the address returned by std::initializer_list<T>::begin()?

提问人:Steven 提问时间:4/29/2023 最后编辑:Steven 更新时间:4/29/2023 访问量:88

问:

我有许多一类数据的静态实例,它们保留整数数组,如下所示:

class ReadableIds
{
public:
  const int * ids;
  ReadableIds( const int * _ids ) : ids(_ids) { }

  // ... a bunch of methods that operate on ids, not germane to the discussion
};

我目前在 filescope 中按以下方式填充它们:

foo.cpp:

//
static const int phase_one_ids[] = { 1, 10, 3, 2, -1 };
static ReadableIds phase_one( phase_one_ids );

static const int phase_two_ids[] = { 31, 11, 23, 542, 11, 88, -1 };
static ReadableIds phase_two( phase_two_ids );

但是,出于易读性的原因,我真的希望它们被内联定义(该类因传递给构造函数的其他参数而更加复杂)

static ReadableIds phase_one( { 1, 10, 3, 2, -1 }, ... other args );
static ReadableIds phase_two( { 31, 11, 23, 542, 11, 88, -1 }, ... other args );

我可以编译它的唯一方法是引入 std::initializer_list:

class ReadableIds
{
public:
    ReadableIds(const std::initializer_list<int> & _ids ) : 
        ids(_ids.begin())  // take address of initializer_list !!
    {
    }
};

但我的理解是 std::initializer_list 是暂时的,因此持有该数据的地址是错误的。

:在不动态分配内存的情况下,是否有解决方案可以实现更好的语法?执行复制会导致静态内存消耗和数据复制,在我的用例中,这是不可行的。

C++ 嵌入式 初始值设定项列表 静态初始化

评论

2赞 Pepijn Kramer 4/29/2023
在构造函数调用期间,它是临时的。您应该将内容复制到成员变量(例如 std::vector)
0赞 Steven 4/29/2023
@PepijnKramer - 在我的用例中,我不想从堆中分配。
2赞 NathanOliver 4/29/2023
值列表将需要位于某个地方。你要么使用数组,这意味着如果你只使用一些默认的最大大小,或者需要成为模板,可能会浪费空间。或者,您可以通过将这些值存储到ReadableIdsvector
0赞 273K 4/29/2023
最好,它允许编译器通过使用 CPU 寄存器来更好地优化代码 - 它通过 CPU 寄存器传递 的 begin 和 end 并直接使用它们,否则代码使用地址的间接。std::initializer_list<int> _ids_ids_ids
2赞 Raymond Chen 4/29/2023
template<std::size_t N> class ReadableIds { public: std::array<int, N> my_ids; ReadableIds(std::array<int, N> const& ids) : my_ids(ids) {} };现在,您在 .my_idsauto x = ReadableIdx(std::array{1,2,3});

答:

1赞 user17732522 4/29/2023 #1

不,你不能存储 的结果,或者更确切地说:你可以存储它,但不能在初始化它成为悬空指针后取消引用它。_ids.begin()phase_two

您的要求似乎是

  1. ReadableIds实际上并不存储元素本身。
  2. 无需添加命名变量来存储元素。
  3. 元素也没有动态存储持续时间。

然后,唯一的方法是将元素存储在临时数组对象中。问题在于,临时性通常在它们显现出来的充分表达之后被摧毁,这不是你想要的。

std::initializer_list构造可以将生存期延长到对象本身的生存期,但是如果您不想命名变量,那么您现在什么也没做,而是将问题转移到延长临时对象的生存期上。std::initializer_liststd::initializer_list

将临时对象的生存期延长到类对象生存期的唯一方法是直接在类中存储对临时对象的引用,并使用新出现的临时通过支撑聚合初始化立即对其进行初始化。

因此,您可以执行以下操作:

// DON'T USE THIS!

class ReadableIds
{
public:
    // must be aggregate, no constructors!

    std::initializer_list<int>&& _ids; // must be reference!
    // other aggregate elements
};

现在您可以使用语法

// DON'T USE THIS!

// must be braces!
static ReadableIds phase_one{ { 1, 10, 3, 2, -1 },
    /*initializers for other aggregate elements*/ };

并且引用的数组将存活到 ,但如果被复制/移动,则不会被复制或延长其生存期!副本将引用相同的数组和对象!显式复制/移动也不会复制/移动或延长底层数组的生命周期!_idsphase_onephase_onestd::initializer_liststd::initializer_list

特别是,如果不是静态存储持续时间对象,则数组不会一直存在到程序结束,只会一直存在到声明的作用域结束。phase_onephase_one

如果使用括号而不是大括号进行初始化或插入构造函数,则生存期延长也不起作用。phase_one

总而言之,我不推荐这个。这是非常脆弱的,不值得避免命名一个额外的变量。(我也不确定编译器是否正确实现了嵌套生存期扩展的这种特殊情况。

此外,重新考虑第一个要求是否真的是必要的。很容易使类成为自动推断内部数组的正确大小的模板。


一个稍微安全的变体是使用数组而不是 .在这种情况下,您需要根据数组的大小对类进行模板化:std::initializer_list

// DON'T USE THIS!

template<std::size_t N>
class ReadableIds
{
public:
    // must be aggregate, no constructors!

    int (&&_ids)[N]; // must be reference!
    // other aggregate elements
};

对初始化的要求仍然适用,但错误复制的可能性较小。_ids