c++ - 传递初始化列表或容器,寻找移动语义?

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

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


C++11 引入了 std::initializer_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 对象不拥有其存储空间(哎呀);它与提供存储的完全独立的裸数组 (yikes) 的关系被模糊地定义 (yikes) 为引用对绑定(bind)临时对象的关系 (quadruple yikes)。

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

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

有资源吗?

最佳答案

it is about what should be passed when the convenient syntax is desired.

如果您想要大小的便利(即:用户只需键入一个没有函数调用或单词的 {} 列表),那么您必须接受所有权力和适当的 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 是一个类,其唯一目的是用于从任意长的相同类型的值列表中初始化特定对象。它在那里充当语言构造(braced-init-list)和这些值要输入的构造函数之间的中间对象。它允许编译器在给定匹配的花括号初始化值列表时调用特定的构造函数(initializer_list 构造函数)。

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

因此,您应该专门将该类用于其设计目的。该类的存在是为了将构造函数标记为从花括号初始化列表中获取值列表。因此,您应该仅将它用于采用此类值的构造函数。

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

此外,您似乎对 initializer_list 和移动有误解。您不能将移出 initializer_list,但您当然可以移入:

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

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

关于c++ - 传递初始化列表或容器,寻找移动语义?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17715553/

相关文章:

c++ - 为什么这是部分特化? (我能做什么?)

c++ - 我从哪里获得 arpa/inet.h?

c# - 将字节数组数据映射到 C# 中的结构

c++ - 正确读取和写入 std::vector 到文件中

c++ - 如何重载空 std::initializer_list?

c++ - 初始化一个包含自身 vector 的结构

c++ - 如何从基类转换为派生类?

c++ - 从 std::string vector 返回 char

c++ - 为什么不使用 is_const 类型特征将 const 引用视为 const?

c++ - 使用 std::initializer_list 时防止缩小转换