传递初始值设定项列表或容器,着眼于移动语义

Pass initializer list or container, looking towards move semantics?

本文关键字:着眼于 语义 移动 列表      更新时间:2023-10-16

编辑:在我们开始之前,这个问题是关于std::initializer_list的正确使用的而不是;它是关于当需要方便的语法时应该传递什么。感谢您继续关注这个话题。


C++11引入std::initializer_list来定义接受支持的init列表参数的函数。

struct bar {
    bar( char const * );
    bar( int );
} dog( 42 );
fn foo( std::initializer_list< bar > args );
foo( { "blah", 3, dog } );

语法很好,但由于各种问题,它令人反感:

  • 它们不能被有意义地移动。上述函数必须从列表中复制dog;无法将其转换为移动构造或删除。完全不能使用仅移动类型。(好吧,const_cast实际上是一个有效的解决方法。如果有关于这样做的文章,我想看看。)

  • 也没有constexpr语义。(这将在C++1y中发布。不过这只是一个小问题。)

  • CCD_ 6不像在其它地方那样传播;CCD_ 7从来不是CCD_。(因为它不拥有自己的内容,所以它不能对副本进行写访问,尽管将其复制到任何地方都不太安全。)

  • initializer_list对象不拥有其存储(yikes);它与提供存储的完全分离的裸阵列(yikes)的关系被模糊地定义为(yike)引用与绑定的临时(四重yikes)之间的关系。

我相信这些事情会在适当的时候得到解决,但目前有没有一种最佳实践可以在不硬编码initializer_list的情况下获得优势?有没有关于直接依赖它的文献或分析?

显而易见的解决方案是传递一个标准容器(如std::vector)的值。一旦对象从initializer_list复制到其中,它就会被移动构造为按值传递,然后您就可以将内容移出。一个改进是在堆栈上提供存储。一个好的库可能能够提供initializer_listarrayvector的大部分优点,甚至不使用前者。

有资源吗?

它是关于当需要方便的语法时应该传递什么。

如果您想要方便的大小(即:用户只需键入一个没有函数调用或单词的{}列表),则必须接受正确initializer_list的所有功能和限制。即使您试图将它转换为其他东西,比如某种形式的array_ref,您仍然必须在它们之间有一个中间initializer_list。这意味着你无法解决你遇到的任何问题,比如无法摆脱它们。

如果它通过initializer_list,那么您必须接受这些限制。因此,另一种选择是不使用initializer_list,这意味着您将不得不接受某种形式的具有特定语义的容器。替代类型必须是一个聚合,这样替代对象的构造就不会遇到同样的问题。

因此,您可能正在考虑强制用户创建一个std::array(或一个语言数组)并传递它。您的函数可以采用某种形式的array_ref类,它可以由任意大小的任何数组构造,因此消耗函数不限于一个大小。

然而,你失去了尺寸的便利性:

foo( { "blah", 3, dog } );

与。

foo( std::array<bar, 3>{ "blah", 3, dog } );

避免这里冗长的唯一方法是让foostd::array作为参数。这意味着它只能采用特定固定大小的数组。您不能使用C++14提出的dynarray,因为它将使用initializer_list中介。

最终,您不应该使用统一的初始化语法来传递值列表。它用于初始化对象,而不是传递事物列表。std::initializer_list是一个类,其唯一目的是用于从相同类型的任意长的值列表中初始化特定对象。它在语言构造(一个支撑的init列表)和这些值要输入的构造函数之间充当中介对象。它允许编译器知道在给定匹配的支持init值列表时调用特定的构造函数(initializer_list构造函数)。

这就是类存在的全部原因。

因此,您应该将该类专门用于它的设计目的。该类的存在是为了将构造函数标记为从支撑的init列表中获取值列表。因此,您应该仅将其用于接受此类值的构造函数。

如果您有一个函数foo,它充当某些内部类型(您不想直接公开)和用户提供的值列表之间的中介,那么您需要将其他内容作为foo的参数。具有您想要的语义的东西,然后您可以将其输入到您的内部类型中。

此外,你似乎对initializer_list和动作有一个误解。您不能将initializer_list中移出,但您可以将<em]移动到>中:

foo( { "blah", 3, std::move(dog) } );

内部dog数组中的第三个条目将被移动构造。