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

Pot*_*ter 11 c++ initializer-list c++11

编辑:在我们开始之前,这个问题不是关于正确使用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 } );
Run Code Online (Sandbox Code Playgroud)

语法很好,但由于各种问题,它很糟糕:

  • 他们无法有意义地感动.上述功能必须dog从列表中复制; 这不能转换为移动建筑或省略.仅移动类型根本无法使用.(好吧,const_cast实际上是一个有效的解决方法.如果有一篇关于这样做的文章,我希望看到它.)

  • 也没有constexpr语义.(这在C++ 1y即将发布.但这是一个小问题.)

  • const不像其他地方那样传播; 的initializer_list是从来没有const,但它的内容始终是.(因为它不拥有其内容,所以它不能对副本进行写访问,尽管将它复制到任何地方都很少是安全的.)

  • initializer_list对象不拥有其存储(yikes); 它与提供存储的完全独立的裸阵列(yikes)的关系被隐含地定义(yikes)作为引用与绑定临时(四倍yikes)的关系.

我相信这些事情会在适当的时候得到解决,但是现在是否有最佳实践来获得没有硬编码的优势initializer_list?是否有关于直接依赖它的文献或分析?

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

有资源吗?

Nic*_*las 6

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

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

如果它通过了initializer_list,那么你必须接受这些限制.因此,替代方案是不经历一个initializer_list,这意味着你将不得不接受某种形式的具有特定语义的容器.替代类型必须是聚合,因此替代对象的构造不会遇到相同的问题.

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

但是,你失去了大小的便利:

foo( { "blah", 3, dog } );
Run Code Online (Sandbox Code Playgroud)

foo( std::array<bar, 3>{ "blah", 3, dog } );
Run Code Online (Sandbox Code Playgroud)

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

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

这就是课程存在的全部原因.

因此,您应该将该类专门用于其设计目的.该类存在于将构造函数标记为从braced-init-list中获取值列表.因此,您应该仅将它用于具有此值的构造函数.

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

而且,你似乎对initializer_lists和运动有误解.您不能移动出来initializer_list,但你肯定可以移动一个:

foo( { "blah", 3, std::move(dog) } );
Run Code Online (Sandbox Code Playgroud)

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

  • @Potatoswatter:"*至于关于initializer_list只能用作构造函数的参数的论点*"我说*应该*,而不是"can"或"can".我回答你应该*做什么,而不是你能*做什么.惯用C++应该避免在构造函数之外使用`initializer_list`."*存在工厂函数成语*",*根本没有统一初始化*.那么你的意思是什么? (3认同)
  • @Potatoswatter:"*我无法相信你根据我的例子使用泛型foo而不是构造函数的nitpick编写了一个完整的答案.*"垃圾输入,垃圾输出.如果您想要回答特定问题,那么您提供的示例实际上需要*代表您想知道的内容.如果您不打算在构造函数之外使用`initializer_list`,那么请不要提供您执行此操作的示例. (3认同)
  • @Potatoswatter:"*你已经回答说语义很糟糕所以它很少被使用*"不,我回答语义很好*因为它的用途*,所以你应该限制自己将它用于那些预期的用途.即构建事物. (3认同)