【问题标题】:Move iterators for containers?移动容器的迭代器?
【发布时间】:2018-08-01 14:58:43
【问题描述】:

C++98 容器定义了两种迭代器,::iterators 和::const_iterators。一般是这样的:

struct vec{
         iterator begin()      ;
   const_iterator begin() const;
};

在 C++11 中,这部分设计似乎没有改变。 问题是, 为了一致性和实用目的,是否也添加 ::move_iterators 有意义? 还是有点矫枉过正。

我可以想象,如果可能,右值容器可能会移动其元素。

class vec{
         iterator begin()      &;
   const_iterator begin() const&;
   move_iterator  begin()     &&;
};

如果我理解正确的话,在简单的情况下可以这样实现:

auto vec::begin() &&{return std::make_move_iterator(this->begin());}

当然可以将普通迭代器转换为移动迭代器(使用std::make_move_iterator),但其动机是通用代码。

例如,使用移动迭代器,这将非常优雅地实现,没有条件取决于参数是左值还是右值。

template<class Container, class T = Container::value_type>
void transport_first(Container&& c, std::vector<T>& v){
    v.emplace_back(*std::forward<Container>(c).begin());
}

请注意,如果可能,此代码不会产生不必要的副本。 如果没有begin生成的move_iterators,这怎么实现。


我也意识到这个问题几乎适用于容器的所有访问者,例如operator[]front()back()

template<class Value>
class vec{
   using value_type       = Value;
   using       reference  = Value&;
   using const_reference  = Value const&;
   using rvalue_reference = Value&&; // NEW!
          reference front()      &{...}
   rvalue_reference front()     &&{...} // NEW!
    const_reference front() const&{...}
};

也许容器应该在 C++11 中从头开始重新设计。 他们的设计正在显示它的时代。


有一个提议,自动推导出(*this)的(decl)类型,基本上所有相应的begin(和其他成员函数)的重载都是免费的。

https://youtu.be/yB4E-SzQPdI?t=4131

【问题讨论】:

    标签: c++11 stl iterator move const-iterator


    【解决方案1】:

    STL 容器旨在与 STL 算法结合使用。目前在 STL 中找到的 通用算法 处理迭代器,但您的模板函数 transport_first:

    template<class Container, class T = Container::value_type>
    void transport_first(Container&& c, std::vector<T>& v){
        v.emplace_back(*std::forward<Container>(c).begin());
    }
    

    基于容器的代码组成,即:它在容器而不是迭代器上运行。由于concepts,我们或许能够找到这种基于容器的算法作为 C++20 的 STL 的一部分。

    通用 transport_first 算法的“类似 STL 的方法”实际上是:

    template<typename InIt, typename OutIt>
    void transport_first(InIt src, OutIt dst) {
        *dst = *src;
    }
    

    按照这种方法(即:迭代器而不是容器),当涉及到泛型代码时,是否使用move_iterator 可以简单地在“最顶层”泛型算法处确定,因为传递的迭代器被进一步复制(即:按值传递到“底层”算法中)。

    此外,与上面基于迭代器的transport_first 不同,STL 充满了基于迭代器对 算法(例如:std::copy)来实现范围。这个接口使得在 rvalues 上重载那些容器成员函数没有什么吸引力,因为正如this other answer 中已经说明的那样,这些重载的成员函数将在未命名(非const)容器上被调用对象和未命名的容器对象仅限于在单个表达式中使用,通过在同一容器对象上同时调用 begin()end() 成员函数来阻碍创建 迭代器对。 p>


    对于容器来说真正有意义的是拥有新的成员函数来返回相应的move_iterator 对象:

    move_iterator Container<T>::mbegin();
    move_iterator Container<T>::mend();
    

    这只是调用std::make_move_iterator 的快捷方式,成员函数返回的值返回iterator。成员函数mbegin() 将在基于迭代器的transport_first 中使用:

    transport_first(coll.mbegin(), std::back_inserter(vec));
    

    相应的函数模板std::mbegin()std::mend() 也有意义:

    transport_first(std::mbegin(coll), std::back_inserter(vec));
    

    【讨论】:

    • 这就是我遵循的路径,首先我添加了mbeginmend(类似于cbegincend)然后我意识到实现begin()&amp;&amp;会很好和end()&amp;&amp; 自动推断出对mbeginmend 的调用(类似于begin() constend() const 所做的),然后我问了这个问题。
    • 见这里:youtu.be/yB4E-SzQPdI?t=4131,通过添加语言特性来拥有begin 的所有重载的建议。
    【解决方案2】:

    您可以轻松地将任何非常量迭代器转换为移动迭代器。它通常对容器没有影响。

    您不能轻易地将非常量迭代器转换为 const 迭代器。例如,一个写时复制字符串(过去对于某些编译器来说是std::string,自定义字符串仍然可能是这个)在采用非常量迭代器(使用begin())时必须悲观地从共享数据中分离出来。为了满足典型的失效保证,当你只需要一个 const 迭代器时,这是非常低效的。

    此外,C++ 重载规则不允许您在不将未指定版本更改为左值的情况下引入 begin() 的右值重载,这将是一项重大更改。

    最后,在右值上重载begin() 无论如何都没有用 - 期望右值函数在右值上被调用,除了std::move 产生的那些,这些右值是 1) 很快就会消失(这会使获得的迭代器)和2)没有名称,这意味着它们只能在一个表达式中使用,这意味着您不能同时调用begin()end()来获取迭代器对,并且单个迭代器是无用的,因为您永远无法知道取消引用它是否安全。

    【讨论】:

    • 我不明白你为什么说“不将未指定的版本更改为左值,那将是一个突破性的变化。”即使这会破坏代码,如果可以重做容器怎么办,这不是一个好主意吗?新设计的容器呢?当然,普通迭代器可以转换为移动迭代器(使用std::make_move_iterator),但其动机是通用代码。知道make_move_iterator是否可以申请基本上有时为时已晚。请参阅我现在提出的问题中的示例代码。
    • 看这里,myfront = *std::forward(cont).begin(); myback = *std::forward(cont).rbegin() 是一个很好的例子,说明为什么这个重载会有用。参见这里 stackoverflow.com/questions/50149972/… ,两次调用 forward (并不意味着对象立即失效,特别是因为调用 begin 不应该首先使任何东西失效)。
    • “不应该”......是的,就像我会相信那样。
    • 讽刺吗?我的意思是begin() 语义上的意思是“指向第一个元素”,当然可以写一个非常刻薄的begin(),但这里的问题是关于当前的一个好的/有用的概括begin() 用于容器。
    • 我是说我不相信在右值引用上调用的任何方法都不会修改对象,我认为这样做是愚蠢的。 (另外,如果容器只包含一个元素,您的代码就会中断。)
    猜你喜欢
    • 2017-03-21
    • 2011-02-11
    • 1970-01-01
    • 1970-01-01
    • 2015-08-14
    • 2015-12-10
    • 2016-01-16
    • 2021-10-16
    • 1970-01-01
    相关资源
    最近更新 更多