【问题标题】:How to properly pass const_iterator& into a function?如何正确地将 const_iterator& 传递给函数?
【发布时间】:2019-11-05 16:58:12
【问题描述】:

假设我有一个整数向量,并希望以一种奇怪的递归方式处理它(如果没有上下文,这种情况可能听起来很奇怪,但仍然如此)。

我想使用 const_iterators 来跟踪当前位置。这里是odd_recursive_stuff()原型:

// Note: changing "std::vector<int>::const_iterator& it_cur"
// into "std::vector<int>::const_iterator it_cur" will change
// the side effects!
void odd_recursive_stuff (std::vector<int>::const_iterator&  it_cur, 
                          std::vector<int>::const_iterator   it_end);

首先我尝试这样称呼它:

void process_vec (const std::vector<int> &vec) {
  odd_recursive_stuff (std::begin(vec), std::end(vec));
}

幸运的是,它没有编译(例如在 clang 8.0.0 中):

Error: no matching function for call to 'recursive_odd_stuff'
Candidate function not viable: expects an l-value for 1st argument!

因为std::begin()返回r-value,所以我不得不称它为另一种有效的方式:

void process_vec (const std::vector<int> &vec) {
   std::vector<int>::const_iterator it_beg = std::begin (vec);
   recursive_odd_stuff (it_beg, std::end(vec));
}

现在我想知道是否可以在没有local_variable it_beg 的单行中调用recursive_odd_stuff() 的基础

似乎不可能编写另一个返回左值的begin() 版本,因为“函数的返回值是左值当且仅当它是引用时(C++03) . (5.2.2 [expr.call] / 10)”。那么唯一的方法就是用两行来调用它?

【问题讨论】:

  • 您能否详细说明为什么需要引用const_iterator
  • 有问题吗?您始终可以将其包装在另一个函数中。也许像recursive_odd_stuffrecursive_odd_stuff_impl 之类的?不,begin 不能按值返回左值(不是引用),因为左值,粗略地说,必须命名。
  • 不要引用可变的 const_iterator,而是考虑返回当前位置,而不是改变调用者的非临时参数。
  • Quimby 解决方案的好处是它把负担放在了执行方,而不是调用方。 (对于一个小项目,可能无关紧要。但在规模上,总是有助于注意强加给调用者而不是被调用者的负担。)
  • 不,有时传递对迭代器的引用(或对任何类型对象的引用)是正确的做法。取决于要实现的目标以及功能的合同。我更喜欢尽可能地遵循价值语义,但这绝不是 C++ 行业中常见的“最佳实践”,它有它的粉丝和不喜欢它的人。

标签: c++ const-iterator


【解决方案1】:

所以,有一种方法可以使它成为单线,但我不推荐它:

#include <vector>
#include <functional>

void odd_recursive_stuff (std::vector<int>::const_iterator&  it_cur, 
                          std::vector<int>::const_iterator   it_end){}

void process_vec (const std::vector<int> &vec) {
  odd_recursive_stuff ([it=std::begin(vec)]()mutable{return std::ref(it);}(), std::end(vec));
}

我认为您的第 n 个递归调用更改了引用,然后第 n-1 个调用者使用该引用来做某事。在这种情况下,我建议将代码拆分为两个函数:

odd_recursive_stuff(IT begin, IT end){
    odd_recursive_stuff_impl(begin, end);
}
odd_recursive_stuff_impl(IT& begin, IT& end){
    ....
}

这暴露了一个只需要迭代器的公共接口。稍后,当您以不需要引用的方式更改算法,或者它也需要 end 作为引用时,您不必更改所有调用。

第一个解决方案可能会扩展为类似于以下内容:

void process_vec (const std::vector<int> &vec) {
    using IT = std::vector<int>::const_iterator;
    struct _lambda_type{
            _lambda_type(const IT& it):_it(it){}

            //By default lambda's () is const method, hence the mutable qualifier.
            std::reference_wrapper<IT> operator()()/*Not const*/{
                return std::ref(_it);
            }
        private:
            IT _it;
    };
    //Previous lines...
    {//The line with the call.
        //Lambda is created before the call and lives until the expression is fully evaluated.
        _lambda_type lambda{std::begin(vec)};
        odd_recursive_stuff (lambda(), std::end(vec));
    }//Here's the lambda destroyed. So the call is perfectly safe.
    //The rest...
}

lambda 的 operator() 返回一个对局部变量的引用,但它是 lambda 对象的局部变量,而不是 operator() 本身。因为 lambda 对象一直存在到表达式结束(;),所以调用是安全的。请注意,我使用std::ref 作为返回引用的快速方法,而无需明确提及返回类型。然后std::reference_wrapper&lt;T&gt; 可以隐式转换为T&amp;

return it; 将按值返回,[it=std::begin(vec)]()mutable -&gt;decltype(it)&amp;{...}; 也不可能。 -&gt;decltype(std::begin(vec))&amp;{ 有效,但它很罗嗦。另一种选择是显式编写迭代器的类型或使用using,但这更糟。

【讨论】:

  • 哇,即使只有一行也可以。谢谢!但“它”不是 lambda 中的局部变量吗?所以你返回一个 ref 到一个局部变量??
  • @gimme_danger 从某种意义上说,是的。我扩展了答案。
【解决方案2】:

重载!

有一个只接受右值的版本:

void odd_recursive_stuff (std::vector<int>::const_iterator&& it_cur, 
                          std::vector<int>::const_iterator   it_end);

…和一个接受左值引用的版本(并为你做额外的行):

void odd_recursive_stuff (const std::vector<int>::const_iterator& it_cur, 
                                std::vector<int>::const_iterator  it_end)
{
    std::vector<int>::const_iterator it_copy(it_cur);
    odd_recursive_stuff(std::move(it_copy), it_end);
}

这与移动语义所依据的原则相同,因为复制和移动构造函数的选择方式相同。

但您可能会考虑放弃这整件事,而只返回 it 的新值:

std::vector<int>::const_iterator
odd_recursive_stuff(std::vector<int>::const_iterator it_cur, 
                    std::vector<int>::const_iterator it_end);

然后你可以随意丢弃它。

没有人真的希望迭代器被引用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-05
    • 2016-01-08
    • 1970-01-01
    相关资源
    最近更新 更多