通过引用传递 C++ 迭代器有什么问题?

What's wrong with passing C++ iterator by reference?

提问人:Adrian McCarthy 提问时间:5/10/2009 最后编辑:KevinAdrian McCarthy 更新时间:9/23/2019 访问量:54797

问:

我用这样的原型编写了一些函数:

template <typename input_iterator>
int parse_integer(input_iterator &begin, input_iterator end);

这个想法是调用方将提供一系列字符,函数将这些字符解释为整数值并返回它,留下最后使用的字符。例如:begin

std::string sample_text("123 foo bar");
std::string::const_iterator p(sample_text.begin());
std::string::const_iterator end(sample_text.end());
int i = parse_integer(p, end);

这将使设置为 123 并“指向”前面的空间。ipfoo

从那以后,我被告知(没有解释)通过引用传递迭代器是不好的形式。形式不好吗?如果是这样,为什么?

C++ 按引用传递 迭代器

评论


答:

6赞 Reunanen 5/10/2009 #1

通常:

如果传递非引用,则调用方不知道迭代器是否正在修改。const

您可以传递引用,但通常迭代器足够小,因此与按值传递相比,它没有优势。const

在您的情况下:

我不认为你所做的事情有什么问题,除了它在迭代器使用方面不是太标准。

-1赞 dirkgently 5/10/2009 #2

您的函数声明的第二个参数缺少引用,是吗?

无论如何,回到你的问题:不,我从来没有读过任何说你不应该通过引用传递迭代器的东西。引用的问题在于它们允许您更改引用的对象。在这种情况下,如果要更改迭代器,则可能会将整个序列搞砸,从而无法进行进一步的处理。

只有一个建议:仔细输入您的参数。

评论

0赞 Adrian McCarthy 5/10/2009
不,第二个参数是故意按值的,因为没有必要通过引用传递它。是的,通过引用传递允许函数更改参数。这就是意图。我不明白你的意思。更改迭代器不能“搞砸整个序列”。更改迭代器与更改区域中的数据不同。毕竟这些是。const_iterators
1赞 anon 5/10/2009 #3

我认为标准库算法仅按值传递迭代器(现在有人会发布一个明显的例外)——这可能是这个想法的起源。当然,没有任何东西说你自己的代码必须看起来像标准库!

2赞 ChrisW 5/10/2009 #4

当他们说“不要通过引用传递”时,这可能是因为将迭代器作为值参数传递更正常/更习惯,而不是通过常量引用传递它们:对于第二个参数,您做到了。

但是,在此示例中,您需要返回两个值:解析的 int 值和新的/修改的迭代器值;鉴于一个函数不能有两个返回代码,将其中一个返回代码编码为非常量引用是 IMO 正常的。

另一种方法是将其编码如下:

//Comment: the return code is a pair of values, i.e. the parsed int and etc ...
pair<int, input_iterator> parse(input_iterator start, input_iterator end)
{
}

评论

0赞 Reunanen 5/10/2009
我也在考虑返回一对,但这需要在应用程序代码上进行一些样板操作......除非你去 boost::tie。
39赞 Johannes Schaub - litb 5/10/2009 #5

没有什么真正的问题,但它肯定会限制模板的使用。你不能只放一个由其他东西返回或生成的迭代器,因为这些都是临时的。你总是首先必须制作一个本地副本,这是某种样板,不是很好。v.begin()

一种方法是使其过载:

int parse_integer(input_iterator begin, input_iterator end, 
                  input_iterator &newbegin);

template<typename input_iterator>
int parse_integer(input_iterator begin, input_iterator end) {
    return parse_integer(begin, end, begin);
} 

另一种选择是有一个输出迭代器,其中数字将被写入:

template<typename input_iterator, typename output_iterator>
input_iterator parse_integer(input_iterator begin, input_iterator end,
                             output_iterator out);

您将获得返回值以返回新的输入迭代器。然后,您可以使用插入器迭代器将解析后的数字放入向量中,或者如果您已经知道数字的数量,则可以使用指针将它们直接放入整数或其数组中。

int i;
b = parse_integer(b, end, &i);

std::vector<int> numbers;
b = parse_integer(b, end, std::back_inserter(numbers));

评论

2赞 Zifre 5/10/2009
“你不能只放一个由其他东西返回或像 v.begin() 这样生成的迭代器,因为这些都是临时的。”这就是 C++0x 右值引用的用途。:)
1赞 iain 5/11/2009
我喜欢输出迭代器的想法,它非常 stl 式的
1赞 Aconcagua 5/9/2016
另一种选择(对于后期读者):返回一个 ::std::p air<int, iterator>,类似于 ::std::map::insert。
3赞 Michael Burr 5/10/2009 #6

在我看来,如果你想这样做,参数应该是指向你将要更改的迭代器的指针。我不是非常量引用参数的忠实拥护者,因为它们隐藏了传递的参数可能会更改的事实。我知道有很多C++用户不同意我的观点 - 这很好。

但是,在这种情况下,迭代器被视为值参数很常见的,我认为通过非常量引用传递迭代器并修改传递的迭代器是一个特别糟糕的主意。它只是违背了迭代器通常使用的惯用方式。

既然有一种很好的方法可以做你想做的事,没有这个问题,我认为你应该使用它:

template <typename input_iterator>
int parse_integer(input_iterator* begin, input_iterator end);

现在,调用方必须执行以下操作:

int i = parse_integer(&p, end);

很明显,迭代器是可以改变的。

顺便说一句,我也喜欢 litb 的建议,即返回新的迭代器并将解析的值放入输出迭代器指定的位置。

3赞 Edward Loper 12/21/2011 #7

在这种情况下,我认为通过引用传递迭代器是完全明智的,只要它有充分的文档记录。

值得注意的是,您的方法(通过引用传递迭代器以跟踪您在标记流时的位置)正是 boost::tokenizer 所采用的方法。具体而言,请参阅 TokenizerFunction 概念的定义。总的来说,我发现 boost::tokenizer 设计得很好,而且经过深思熟虑。