通过引用传递 C++ 迭代器有啥问题?
Posted
技术标签:
【中文标题】通过引用传递 C++ 迭代器有啥问题?【英文标题】:What's wrong with passing C++ iterator by reference?通过引用传递 C++ 迭代器有什么问题? 【发布时间】:2010-10-25 01:06:27 【问题描述】:我用这样的原型编写了一些函数:
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);
这将使i
设置为123,p
“指向”foo
之前的空格。
我被告知(没有解释)通过引用传递迭代器是不好的形式。是不是形式不好?如果有,为什么?
【问题讨论】:
【参考方案1】:一般:
如果传递非const
引用,调用者不知道迭代器是否正在被修改。
您可以传递 const
引用,但通常迭代器足够小,与按值传递相比没有优势。
在你的情况下:
我不认为你所做的有什么问题,只是它在迭代器的使用方面不太标准。
【讨论】:
【参考方案2】:您的函数声明的第二个参数缺少引用,是吗?
不管怎样,回到你的问题:不,我从来没有读过任何说你不应该通过引用传递迭代器的东西。引用的问题在于它们允许您更改引用的对象。在这种情况下,如果您要更改迭代器,您可能会将整个序列搞砸,从而导致无法进行进一步处理。
只有一个建议:仔细输入您的参数。
【讨论】:
不,第二个参数是故意按值传递的,因为不需要通过引用传递它。是的,通过引用传递允许函数更改参数。这就是意图。我不明白你的意思。更改迭代器不能“搞砸整个序列”。更改迭代器与更改范围内的数据不同。毕竟这些是const_iterators
。【参考方案3】:
我认为标准库算法仅按值传递迭代器(现在有人会发布一个明显的例外) - 这可能是这个想法的起源。当然,没有什么说你自己的代码必须看起来像标准库!
【讨论】:
【参考方案4】:当他们说“不要通过引用传递”时,这可能是因为将迭代器作为值参数传递而不是通过 const 引用传递它们更正常/惯用:对于第二个参数,您这样做了。
然而,在本例中,您需要返回两个值:解析后的 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)
【讨论】:
我也在考虑返回一对,但这需要应用程序代码上的一些样板文件......除非你选择 boost::tie。【参考方案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));
【讨论】:
“你不能只放一个由其他东西返回的迭代器,或者像 v.begin() 这样生成的迭代器,因为它们是临时的。”这就是 C++0x 右值引用的用途。 :) 我喜欢输出迭代器的想法,它非常stl-esque 另一种选择(对于后期读者):返回 ::std::pair在我看来,如果您想这样做,参数应该是指向您将要更改的迭代器的指针。我不是非常量引用参数的忠实粉丝,因为它们隐藏了传递的参数可能会更改的事实。我知道有很多 C++ 用户不同意我对此的看法——这很好。
但是,在这种情况下,将迭代器视为值参数是很常见的,我认为通过非常量引用传递迭代器并修改传递的迭代器是一个特别糟糕的主意。它只是违背了通常使用迭代器的惯用方式。
既然有一种很好的方法可以做你想做的事而没有这个问题,我认为你应该使用它:
template <typename input_iterator>
int parse_integer(input_iterator* begin, input_iterator end);
现在调用者必须这样做:
int i = parse_integer(&p, end);
很明显,迭代器是可以改变的。
顺便说一句,我也喜欢litb's suggestion 返回新的迭代器并将解析的值放入输出迭代器指定的位置。
【讨论】:
【参考方案7】:在这种情况下,我认为通过引用传递迭代器是完全明智的,只要它有充分的文档。
值得注意的是,您的方法(通过引用传递迭代器以跟踪您在标记流时所处的位置)正是 boost::tokenizer 采用的方法。具体参见TokenizerFunction Concept 的定义。总的来说,我发现 boost::tokenizer 设计得非常好,而且经过深思熟虑。
【讨论】:
以上是关于通过引用传递 C++ 迭代器有啥问题?的主要内容,如果未能解决你的问题,请参考以下文章