编辑:在我们开始之前,关于正确使用std::initializer_list
的问题 它是关于在需要方便的语法时应该传递什么。感谢您留下主题。
C ++ 11引入std::initializer_list
来定义接受braced-init-list参数的函数。
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即将发布。但这是一个小问题。)
const
不会像其他地方一样传播; initializer_list
永远不会const
,但其内容始终是initializer_list
。 (因为它不拥有其内容,所以它无法对副本进行写访问,尽管将其复制到任何地方都很少是安全的。)
initializer_list
对象不拥有其存储空间(yikes);它与提供存储的完全独立的裸阵列(yikes)之间的关系被定义为(yikes)作为引用与绑定临时(四倍yikes)的关系。
我相信这些事情会在适当的时候得到解决,但是目前是否有最佳做法可以在没有硬编码的情况下获得优势std::vector
?是否有关于直接依赖它的文献或分析?
显而易见的解决方案是通过值传递标准容器,例如initializer_list
。一旦从initializer_list
将对象复制到其中,它就会被移动构造为按值传递,然后您可以移出内容。改进是在堆栈上提供存储。一个好的图书馆可能能够提供array
,vector
和{{1}}的大部分优势,甚至不使用前者。
任何资源?
答案 0 :(得分:6)
它是关于在需要方便的语法时应该传递什么。
如果你想要大小的便利性(即:用户只需键入一个没有函数调用或单词的{}
列表),那么你必须接受a的所有权限和限制适当的initializer_list
。即使您尝试将其转换为其他内容,例如某种形式的array_ref
,您仍然需要在它们之间设置中间人initializer_list
。这意味着您无法解决您遇到的任何问题,例如无法摆脱它们。
如果它通过initializer_list
,那么你必须接受这些限制。因此,替代方法是不通过initializer_list
,这意味着您将不得不接受具有特定语义的某种形式的容器。替代类型必须是聚合,因此替代对象的构造不会遇到同样的问题。
因此,您可能正在考虑强制用户创建std::array
(或语言数组)并传递它。你的函数可以采用某种形式的array_ref
类,它可以从任意大小的任何数组构造,因此消费函数不限于一种大小。
然而,你失去了尺寸的便利性:
foo( { "blah", 3, dog } );
VS
foo( std::array<bar, 3>{ "blah", 3, dog } );
避免这种详细程度的唯一方法是让foo
将std::array
作为参数。这意味着它只能采用特定固定大小的数组。并且您无法使用C ++ 14提议的dynarray
,因为它将使用initializer_list
中介。
最终,您不应该使用统一的初始化语法来传递值列表。它用于初始化对象,而不是用于传递事物列表。 std::initializer_list
是一个类,其唯一目的是用于从任意长的相同类型的值列表中初始化特定对象。它用作语言构造(braced-init-list)和构造函数之间的中间对象,这些构造函数将被输入。它允许编译器在给定匹配的braced-init-values值列表时知道调用特定的构造函数(initializer_list
构造函数)。
这就是课程存在的全部原因。
因此,您应该将该类专门用于其设计目的。该类存在于将构造函数标记为从braced-init-list中获取值列表。因此,您应该仅将它用于具有此值的构造函数。
如果你有一些函数foo
充当某个内部类型(你不想直接暴露)和用户提供的值列表之间的中介,那么你需要采取其他的东西作为foo
的参数。具有您想要的语义的东西,然后您可以将其输入内部类型。
另外,你似乎对initializer_list
和运动有误解。您无法将移出 initializer_list
,但您可以将移动到中:
foo( { "blah", 3, std::move(dog) } );
内部dog
数组中的第三个条目将被移动构造。