传递初始化列表或容器,看向移动语义?

时间:2013-07-18 05:55:21

标签: c++ c++11 initializer-list

编辑:在我们开始之前,关于正确使用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将对象复制到其中,它就会被移动构造为按值传递,然后您可以移出内容。改进是在堆栈上提供存储。一个好的图书馆可能能够提供arrayvector和{{1}}的大部分优势,甚至不使用前者。

任何资源?

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 } );

避免这种详细程度的唯一方法是让foostd::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数组中的第三个条目将被移动构造。