简体   繁体   English

对于具有线性存储的容器,可以使用原始指针代替带有 STL 算法的迭代器吗?

[英]Can raw pointers be used instead of iterators with STL algorithms for containers with linear storage?

I have a custom vector container that internally stores item a linear array.我有一个自定义向量容器,它在内部存储一个线性数组项。 Last night, I was trying to implement custom iterators for my class to be able to use them with STL algorithms.昨晚,我试图为我的类实现自定义迭代器,以便能够将它们与 STL 算法一起使用。 I have had some success that you can see in here:我已经取得了一些成功,你可以在这里看到:

Live example with custom iterators带有自定义迭代器的实时示例

While doing so, I discovered I can merely pass raw pointers to STL algorithm and they just seem to work fine.这样做时,我发现我只能将原始指针传递给 STL 算法,而且它们似乎工作正常。 Here's the example without any iterators:这是没有任何迭代器的示例:

#include <cstddef>
#include <iostream>
#include <iterator>
#include <algorithm>

template<typename T>
class my_array{
    T* data_;
    std::size_t size_;

public:

    my_array()
        : data_(NULL), size_(0)
    {}
    my_array(std::size_t size)
        : data_(new T[size]), size_(size)
    {}
    my_array(const my_array<T>& other){
        size_ = other.size_;
        data_ = new T[size_];
        for (std::size_t i = 0; i<size_; i++)
            data_[i] = other.data_[i];
    }
    my_array(const T* first, const T* last){
        size_ = last - first;
        data_ = new T[size_];

        for (std::size_t i = 0; i<size_; i++)
            data_[i] = first[i];
    }

    ~my_array(){
        delete [] data_;
    }
    const my_array<T>& operator=(const my_array<T>& other){
        size_ = other.size_;
        data_ = new T[size_];
        for (std::size_t i = 0; i<size_; i++)
            data_[i] = other.data_[i];
        return other;
    }
    const T& operator[](std::size_t idx) const {return data_[idx];}
    T& operator[](std::size_t& idx) {return data_[idx];}
    std::size_t size(){return size_;}

    T* begin(){return data_;}
    T* end(){return data_+size_;}
};

template<typename T>
void print(T t) {
    std::cout << t << std::endl;
}

int main(){


    typedef float scalar_t;
    scalar_t list [] = {1, 3, 5, 2, 4, 3, 5, 10, 10};
    my_array<scalar_t> a(list, list+sizeof(list)/sizeof(scalar_t));

    // works!
    for (scalar_t* it = a.begin(), *end = a.end();
         it != end; ++it)
        std::cout << ' ' << *it;
    std::cout << std::endl;

    // works!
    std::for_each(a.begin(), a.end(), print<scalar_t>);
    std::cout << std::endl;

    // works!
    my_array<int> b(a.size());
    std::copy(a.begin(), a.end(), b.begin());

    // works!
    scalar_t* end = std::remove(a.begin(), a.end(), 5);
    std::for_each(a.begin(), end, print<scalar_t>);
    std::cout << std::endl;

    // works!
    std::random_shuffle(a.begin(), end);
    std::for_each(a.begin(), end, print<scalar_t>);
    std::cout << std::endl;

    // works!
    std::cout << "Counts of 3 in array = " << std::count(a.begin(), end, 3) << std::endl << std::endl;

    // works!
    std::sort(a.begin(), end);
    std::for_each(a.begin(), end, print<scalar_t>);
    std::cout << std::endl;

    // works!
    if (!std::binary_search(a.begin(), a.end(), 5))
        std::cout << "Removed!" << std::endl;

    return 0;
}

Live example without iterators没有迭代器的实时示例

My questions here are the following:我的问题如下:

  1. Does this always work for containers that have linear storage?这是否总是适用于具有线性存储的容器? I know that this would not work for linked-lists for example.例如,我知道这不适用于链表。
  2. If they do work in this situation, why should I ever go through the hassle of implementing iterators anyway?如果它们确实在这种情况下工作,我为什么还要经历实现迭代器的麻烦? I know how iterators generalize my code and whatnot, but if this simple array is all I ever need then I don't see the point.我知道如何迭代器概括我的代码和诸如此类的东西,但如果这个简单的数组我永远需要那么我不明白这一点。
  3. What are the negative issues of what I'm doing if this approach would always work?如果这种方法总是有效,我正在做的事情有哪些负面问题? For one thing, I can see I'm breaking data encapsulation.一方面,我可以看到我正在破坏数据封装。

One of the features of iterators being based on operator-overloading, is that pointers are already random-access iterators.基于运算符重载的迭代器的特征之一是指针已经是随机访问迭代器。 This was a big design win in the early days of STL, as it made it easier to use algorithms with existing code (as well as making the interface more familiar to programmers).这是 STL 早期的一个重大设计胜利,因为它可以更轻松地将算法与现有代码一起使用(并使程序员更熟悉界面)。 It's perfectly reasonable to wrap an array, add typedef T* iterator; typedef const T* const_iterator包装一个数组,添加typedef T* iterator; typedef const T* const_iterator是完全合理的typedef T* iterator; typedef const T* const_iterator typedef T* iterator; typedef const T* const_iterator , return &array[0] from your begin() and &array[size] from your end() , and then use your container with any iterator-based algorithm. typedef T* iterator; typedef const T* const_iterator ,返回&array[0]begin()&array[size]end()然后使用您的容器与任何基于迭代器的算法。 As you already realise, this will work for any container where elements are contiguous in memory (such as an array).正如您已经意识到的,这适用于任何元素在内存中是连续的容器(例如数组)。

You might implement 'real' iterators if:如果出现以下情况,您可能会实现“真正的”迭代器:

  • You have a different-shaped container (such as a tree or list);您有一个不同形状的容器(例如树或列表);
  • You want to be able to resize the array without invalidating the iterators;您希望能够在不使迭代器失效的情况下调整数组大小;
  • You want to add debugging checks to your iterator use, for example to check if the iterator is used after being invalidated or after the container has been deleted, or to bounds-check;您想在迭代器使用中添加调试检查,例如检查迭代器是否在失效或容器被删除后使用,或者边界检查;
  • You want to introduce type-safety, and make sure people can't accidentally assign an arbitrary T* to a my_array::iterator .您想引入类型安全性,并确保人们不会意外地将任意T*分配给my_array::iterator

I'd say this last advantage alone is well worth writing a trivial wrapper class for.我想说仅此最后一个优势就值得为其编写一个简单的包装类。 If you don't take advantage of C++'s type system by making different kinds of thing have different types, you might as well switch to Javascript :-)如果您没有通过使不同类型的事物具有不同类型来利用 C++ 的类型系统,那么您不妨切换到 Javascript :-)

  1. Yes.是的。 See Effective STL, Item 16 , which demonstrates with linear storage containers you can simply take the address of an item and work with that pointer as if it pointed to a simple array.请参阅Effective STL, Item 16 ,它演示了使用线性存储容器,您可以简单地获取项目的地址并使用该指针,就像它指向一个简单的数组一样。
  2. I think you answered your own question – you probably shouldn't, if you know the simple array is all you'll ever need.我想你已经回答了你自己的问题——你可能不应该,如果你知道简单的数组就是你所需要的。
  3. Probably the biggest issue is just that – breaking data encapsulation.可能最大的问题就是——破坏数据封装。 Consider whether or not an abstraction such as an explicit iterator type would buy you anything versus the cost.考虑诸如显式迭代器类型之类的抽象是否会为您购买任何东西而不是成本。

It happens that pointers provide the interface required of random access iterators (dereference, increment, addition, difference, etc) and can be treated just like iterators.碰巧的是,指针提供了随机访问迭代器(解引用、增量、加法、差值等)所需的接口,并且可以像迭代器一样对待。

  1. It should always work for containers with contiguous storage.它应该始终适用于具有连续存储的容器。
  2. You might wish to create your own iterators for the same reason you use methods instead of all public data in your classes: To encapsulate what's happening with an interface you can modify if you need to.您可能希望创建自己的迭代器,原因与您在类中使用方法而不是所有公共数据的原因相同:要封装接口发生的事情,您可以根据需要进行修改。 As long as you typedef your T* to an iterator type this is probably not a significant issue.只要您将T*类型定义为迭代器类型,这可能不是一个重要问题。 Additionally some algorithms may benefit from an iterator that's tagged with the iterator type, which you can't do for simple pointer types.此外,某些算法可能会受益于标记有迭代器类型的迭代器,而对于简单的指针类型则无法做到这一点。

Does this always work for containers that have linear storage?这是否总是适用于具有线性存储的容器?

Yes, the iterator concepts were designed so that pointers could act as iterators over arrays.是的,迭代器概念的设计是为了让指针可以充当数组上的迭代器。

If they do work in this situation, why should I ever go through the hassle of implementing iterators anyway?如果它们确实在这种情况下工作,我为什么还要经历实现迭代器的麻烦?

There's no good reason to define your own iterator type in this situation, unless you want to do something like bounds-checking which can't be done with a simple pointer.在这种情况下没有充分的理由定义您自己的迭代器类型,除非您想执行诸如边界检查之类的操作,而这种操作无法通过简单的指针来完成。

One slight benefit would be that you could include nested typedefs for the iterator's traits, as some of the standard iterator types do;一个小小的好处是您可以为迭代器的特征包含嵌套的 typedef,就像一些标准迭代器类型所做的那样; but using pointers these are available from std::iterator_traits<T*> anyway.但是无论如何使用指针,这些都可以从std::iterator_traits<T*>

What are the negative issues of what I'm doing if this approach would always work?如果这种方法总是有效,我正在做的事情有哪些负面问题? For one thing, I can see I'm breaking data encapsulation.一方面,我可以看到我正在破坏数据封装。

To make the interface more consistent with STL-style containers, you should define iterator and const_iterator types ( typedef aliases for the pointers), and provide const overloads of begin and end ;为了使接口与 STL 风格的容器更加一致,您应该定义iteratorconst_iterator类型(指针的typedef别名),并提供beginend const重载; and perhaps cbegin and cend for C++11 compatiblity.也许cbegincend为C ++ 11的兼容性。

There are various other requirements that you might want to conform to;您可能希望遵守其他各种要求; see section 23.2 of the C++ standard for the gory details.有关详细信息,请参阅 C++ 标准的第 23.2 节。 But in general, it's more important to make iterators conform to their requirements, since STL-style algorithms work with iterators rather than containers, and by using pointers you already conform to those requirements.但总的来说,使迭代器符合它们的要求更为重要,因为 STL 风格的算法使用迭代器而不是容器,并且通过使用指针,您已经符合这些要求。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM