倾向于过度指针的迭代器?

时间:2015-03-17 12:30:05

标签: c++ string iterator char-pointer const-pointer

This question是一个问题,其中有一条评论here,但已被删除作为影片的一部分。

对于那些无法看到已删除帖子的人,评论是我在this answer中使用const char*而不是string::const_iterator:"迭代器从一开始就可能是一条更好的道路,因为看起来你的指针似乎正是如此。"

所以我的问题是,迭代器是否保持string::const_iteratorconst char* s持有任何内在价值,以便将我的答案转换为string::const_iterators是否有意义?

2 个答案:

答案 0 :(得分:6)

简介

使用迭代器而不是指针有许多好处,其中包括:

  • 发布中的不同代码路径 vs debug ,以及;
  • 更好的类型安全,和;
  • 可以编写通用代码(迭代器可以用于任何数据结构,例如链表,而内部指针在这方面非常有限)。


调试

除其他外,解除引用传递范围结束的迭代器是 undefined-behavior ,在这种情况下,实现可以自由地做任何必要的事情 - 包括提高诊断说你做错了什么。

gcc 提供的标准库实现libstdc++将在检测到错误时启动诊断(如果启用Debug Mode)。


<子> 示例

#define _GLIBCXX_DEBUG 1 /* enable debug mode */

#include <vector>
#include <iostream>

int
main (int argc, char *argv[])
{
  std::vector<int> v1 {1,2,3};

  for (auto it = v1.begin (); ; ++it)
    std::cout << *it;
}
/usr/include/c++/4.9.2/debug/safe_iterator.h:261:error: attempt to 
    dereference a past-the-end iterator.

Objects involved in the operation:
iterator "this" @ 0x0x7fff828696e0 {
type = N11__gnu_debug14_Safe_iteratorIN9__gnu_cxx17__normal_iteratorIPiNSt9__cxx19986vectorIiSaIiEEEEENSt7__debug6vectorIiS6_EEEE (mutable iterator);
  state = past-the-end;
  references sequence with type `NSt7__debug6vectorIiSaIiEEE' @ 0x0x7fff82869710
}
123

如果我们使用指针,无论我们是否处于调试模式,都不会发生上述情况。

如果我们没有为 libstdc ++ 启用调试模式,将使用更加性能友好的版本(没有添加簿记)实现 - 并且不会发出任何诊断信息



(可能)更好的类型安全性

由于迭代器的实际类型是实现定义的,这可用于提高类型安全性 - 但您必须检查实现的文档看看是否是这种情况。


考虑以下示例:

#include <vector>

struct A     { };
struct B : A { };

                                                      // .-- oops
                                                      // v
void  it_func (std::vector<B>::iterator beg, std::vector<A>::iterator end);

void ptr_func (B * beg, A * end);
                     // ^-- oops

int
main (int argc, char *argv[])
{
  std::vector<B> v1;

   it_func (v1.begin (), v1.end  ());               // (A)
  ptr_func (v1.data  (), v1.data () + v1.size ());  // (B)
}

<子>的精化

    根据实现,
  • (A)可能是编译时错误,因为std::vector<A>::iteratorstd::vector<B>::iterator可能不是同一类型。
  • 然而,
  • (B)总是会编译,因为存在从B*A*的隐式转换。

答案 1 :(得分:3)

迭代器旨在提供指针的抽象。

例如,递增迭代器总是操纵迭代器,这样如果集合中有下一个项目,它就会引用下一个项目。如果它已经引用了集合中的最后一项,则在增量之后它将是一个不能被解除引用的唯一值,但是将比较等于指向同一集合末尾的另一个迭代器(通常用collection.end())获得。

在迭代器到字符串(或向量)的特定情况下,指针提供迭代器所需的所有功能,因此指针可以用作迭代器而不会丢失所需的功能。

例如,您可以使用std::sort对字符串或向量中的项目进行排序。由于指针提供了所需的功能,您还可以使用它来对本机(C风格)数组中的项进行排序。

同时,是的,定义(或使用)与指针分离的迭代器可以提供不严格要求的额外功能。例如,一些迭代器提供至少一定程度的检查,以确保(例如)当您比较两个迭代器时,它们将两个迭代器都放入同一个集合中,并且您不会尝试输出边界访问。原始指针不能(或至少通常不会)提供这种能力。

这大部分都归结为“不要用你不用的东西”#34;心理。如果您真的只需要并希望本机指针的功能,它们可以用作迭代器,并且您通常会获得与您通过直接操作指针获得的代码基本相同的代码。同时,对于需要额外功能的情况,例如遍历线程RB树或B +树而不是简单数组,迭代器允许您在维护单个简单接口的同时执行此操作。同样地,对于您不介意额外支付(在存储和/或运行时方面)以获得额外安全性的情况,您也可以得到它(并且它与单个算法之类的东西分离,所以你可以把它拿到你想要的地方,而不必被迫在其他地方使用它,例如,它可能过于批评时间要求来支持它。

在我看来,很多人都会在迭代器方面忽视这一点。许多人高兴地重写了类似的内容:

for (size_t i=0; i<s.size(); i++)

...进入类似的事情:

for (std::string::iterator i = s.begin; i != s.end(); i++)

......并表现得好像是一项重大成就。我不这么认为。对于这样的情况,使用迭代器替换整数类型可能很少(如果有的话)。同样,获取您发布的代码并将char const *更改为std::string::iterator似乎不太可能取得很大成就(如果有的话)。实际上,这种转换通常会使代码更加冗长,更难以理解,而不会获得任何回报。

如果您要更改代码,您应该(在我看来)这样做,试图通过使其真正通用(std::string::iterator真的不会做它来使它更通用)。

例如,考虑一下您的split(从您关联的帖子中复制):

vector<string> split(const char* start, const char* finish){
    const char delimiters[] = ",(";
    const char* it;
    vector<string> result;

    do{
        for (it = find_first_of(start, finish, begin(delimiters), end(delimiters));
            it != finish && *it == '(';
            it = find_first_of(extractParenthesis(it, finish) + 1, finish, begin(delimiters), end(delimiters)));
        auto&& temp = interpolate(start, it);
        result.insert(result.end(), temp.begin(), temp.end());
        start = ++it;
    } while (it <= finish);
    return result;
}

目前,这仅限于在窄弦上使用。如果有人想使用宽字符串,UTF-32字符串等,那么要做到这一点相对困难。同样,如果有人想匹配[或&#39; {&#39;而不是(,代码也需要重写代码。

如果有可能想要支持各种字符串类型,我们可能希望使代码更通用,如下所示:

template <class InIt, class OutIt, class charT>
void split(InIt start, InIt finish, charT paren, charT comma, OutIt result) {
    typedef std::iterator_traits<OutIt>::value_type o_t;
    charT delimiters[] = { comma, paren };
    InIt it;

    do{
        for (it = find_first_of(start, finish, begin(delimiters), end(delimiters));
            it != finish && *it == paren;
            it = find_first_of(extractParenthesis(it, finish) + 1, finish, begin(delimiters), end(delimiters)));
        auto&& temp = interpolate(start, it);
        *result++ = o_t{temp.begin(), temp.end()};
        start = ++it;
    } while (it != finish);
}

这还没有经过测试(甚至编译),所以它实际上只是你可以获取代码的一般方向的草图,而不是实际的完成代码。尽管如此,我认为一般的想法至少应该是显而易见的 - 我们不能将其改为&#34;使用迭代器&#34;。我们将其更改为通用,并且迭代器(作为模板参数传递,类型未在此处直接指定)仅是其中的一部分。为了达到很远,我们还消除了对paren和逗号字符的硬编码。虽然不是绝对必要,但我也更改了参数以更符合标准算法使用的约定,因此(例如)输出也是通过迭代器编写的,而不是作为集合返回。

虽然可能不会立即显现,但后者确实增加了相当多的灵活性。例如,如果某人只是想在分割后打印出字符串,他可以通过std::ostream_iterator,将每个结果直接写入std::cout生成,而不是获取一个字符串向量,然后分别打印出来。