在自定义容器类

时间:2017-02-14 08:16:04

标签: c++ for-loop iterator c++14 reverse-iterator

我试图通过移植Sedgewick和Wayne的算法,第4版中的主要示例来提高我的C ++技能。我根据他们的Java example编写了一个通用的堆栈实现。

我的堆栈工作正常,但我想提高性能并试图编写反向迭代器。

template<typename T> class ResizingArrayStack {
public:
    T* begin() { return &array_ptr[0]; }
    T* end() { return &array_ptr[N]; }

...

// Here we're iterating forward through the array, with an unused variable `i`.
// It would be nice performance-wise to iterate in reverse without calling pop(), and without triggering a resize.
for ( auto& i : lifo_stack ) {
    cout << "Current loop iteration has i = " << i << endl;
}
// // Alternatively, pop from the stack N times.
// cout << "Popped an item from the stack: " << lifo_stack.pop() << endl;

我尝试切换上面的beginend成员函数,但发现展开的for循环始终以++__begin递增,即使__end处于较低位置内存地址。我们怎样才能使i反向循环(相对于堆栈的LIFO)?

如果有严重的错误或方面看起来过时,请随时评论我的代码风格。我希望与优秀的现代人保持一致。 C ++。

6 个答案:

答案 0 :(得分:6)

如果要使用带有反向迭代器的range-for循环,可以使用存储范围的包装类Reverse并返回与reverse_iterator对应的begin和{ {1}}

end

Live Example

请注意,这使用C ++ 1z模板推导,仅受g ++ 7.0 SVN和clang 5.0 SVN支持。对于早期的编译器,您可以添加辅助函数

#include <iostream>
#include <iterator>
#include <vector>

template<class Rng>
class Reverse
{
    Rng const& rng;    
public:    
    Reverse(Rng const& r) noexcept
    : 
        rng(r)
    {}

    auto begin() const noexcept { using std::end; return std::make_reverse_iterator(end(rng)); }
    auto end()   const noexcept { using std::begin; return std::make_reverse_iterator(begin(rng)); }
};

int main()
{
    std::vector<int> my_stack;
    my_stack.push_back(1);
    my_stack.push_back(2);
    my_stack.push_back(3);

    // prints 3,2,1
    for (auto const& elem : Reverse(my_stack)) {
        std::cout << elem << ',';    
    }
}

Live Example(适用于gcc 5.1或clang 3.5)

或者,您可以使用Boost.Range library并简单地执行(将使用任何C ++ 11编译器)

    template<class Rng>
    auto MakeReverse(Rng const& rng) { return Reverse<Rng>(rng); }

    for (auto const& elem : MakeReverse(my_stack)) {
        std::cout << elem << ',';    
    }

Live Example

请注意,您必须小心将临时变量传递给此类适配器,我和Boost适配器在传递时都不起作用。 @Pixelchemist在评论中指出的原始#include <iostream> #include <vector> #include <boost/range/adaptor/reversed.hpp> int main() { std::vector<int> my_stack; my_stack.push_back(1); my_stack.push_back(2); my_stack.push_back(3); for (auto const& elem : boost::adaptors::reverse(my_stack)) { std::cout << elem << ','; } }

答案 1 :(得分:1)

这里有一个问题的划痕。不要将此视为工作代码。使用它来了解如何实现反向迭代器(只有很多可能的方法)。

template<typename T> class ResizingArrayStack {
public: 
    class reverse_iterator
    {       
        ResizingArrayStack & _storage;
        int _pointer;

    public:
        inline reverse_iterator(ResizingArrayStack & storage,
                                int pointer)
            : _storage(storage)
            , _pointer(pointer)
        {}

        inline reverse_iterator & operator++() // prefix
        {
            --_pointer;
            return *this;
        }

        inline reverse_iterator operator++() // postfix
        {
            reverse_iterator tmp(*this);
            --_pointer;
            return tmp;
        }

        inline T & operator*()
        {
            return _storage.getByIndex(_pointer);
        }

        // TODO: == != etc
    };      

    reverse_iterator rbegin() { return reverse_iterator(*this, N - 1); }
    reverse_iterator rend() { return reverse_iterator(*this, -1); }
    // ...  //
};

答案 2 :(得分:1)

此解决方案不会引入不必要的副本,也不会出现某些注释所建议的错误转发。以下说明。

您可以使用一些具有实际开始和结束功能的包装器 返回反向迭代器。

template<class T>
struct revert_wrapper
{
    T o;
    revert_wrapper(T&& i) : o(std::forward<T>(i)) {}
};

template<class T>
auto begin(revert_wrapper<T>& r)
{
    using std::end;
    return std::make_reverse_iterator(end(r.o));
}

template<class T>
auto end(revert_wrapper<T>& r)
{
    using std::begin;
    return std::make_reverse_iterator(begin(r.o));
}

template<class T>
auto begin(revert_wrapper<T> const& r) 
{ 
    using std::end;
    return std::make_reverse_iterator(end(r.o));
}

template<class T>
auto end(revert_wrapper<T> const& r)
{
    using std::begin;
    return std::make_reverse_iterator(begin(r.o));
}

template<class T>
auto reverse(T&& ob)
{
    return revert_wrapper<T>{ std::forward<T>(ob) };
}

像这样使用:

std::vector<int> v{1, 2, 3, 4};
for (auto i : reverse(v))
{
    std::cout << i << "\n";
}

或在你的情况下

for ( auto& i : reverse(lifo_stack) ) {
    cout << "Current loop iteration has i = " << i << endl;
    cout << "Popped an item from the stack: " << lifo_stack.pop() << endl;
}

由于传播不是一个容易的话题,而且存在误解,我会进一步解释一些细节。我将使用std::vector<int>作为“要反转”类型T的示例。

1。功能模板reverse

1.1传递左值std::vector<int>

std::vector<int> v{1, 2, 3, 4};
auto&& x = reverse(v);

在这种情况下,编译器创建的reverse实例如下所示:

template<>
auto reverse<std::vector<int>&>(std::vector<int>& ob)
{
    return revert_wrapper<std::vector<int>&>{ std::forward<std::vector<int>&>(ob) };
}

我们在这里看到两件事:

  • T的{​​{1}}将为revert_wrapper,因此不涉及任何副本。
  • 我们将左值作为左值转发给std::vector<int>&
  • 的构造函数

1.2传递右值revert_wrapper

std::vector<int>

我们再次查看函数模板的实例化:

std::vector<int> foo();
auto&& x = reverse(foo());

并且可以再次注意两件事:

  • template<> auto reverse<std::vector<int>>(std::vector<int>&& ob) { return revert_wrapper<std::vector<int>>{ std::forward<std::vector<int>>(ob) }; } 的{​​{1}}将为T,从而复制向量,防止在任何基于范围的循环运行之前rvalue超出范围
  • 右下角revert_wrapper将转发给std::vector<int>
  • 的构造函数

2。类模板std::vector<int>&&及其构造函数

2.1 revert_wrapper revert_wrapper revert_wrapper reverse std::vector<int>& {/ 1}}
template<>
struct revert_wrapper<std::vector<int>&>
{
    std::vector<int>& o;
    revert_wrapper(std::vector<int>& i) : 
        o(std::forward<std::vector<int>&>(i)) {}
};

如上所述:我们存储参考时不涉及任何副本。 forward似乎也很熟悉,实际上它与reverse中的内容相同:我们将左值作为左值引用转发。

2.2如果是右revert_wrapper

reverse创建的std::vector<int>&&
template<>
struct revert_wrapper<std::vector<int>>
{
    std::vector<int> o;
    revert_wrapper(std::vector<int>&& i) : 
        o(std::forward<std::vector<int>>(i)) {}
};

这次我们按值存储对象以防止悬空引用。 转发也很好:我们将右值引用从reverse转发到revert_wrapper构造函数,然后将其转发到std::vector构造函数。我们可以以相同的方式使用static_cast<T&&>(i),但我们不是(std::)mov(e)来自左值,我们正在转发:

  • 左值作为左值和
  • rvalues as rvalues。

我们还可以在这里再看一件事: 按值存储的revert_wrapper实例唯一可用的构造函数采用右值。因此,我们不能(轻易)欺骗这个类来制作不必要的副本。

请注意,在std::forward构造函数中std::move的初始值设定项中使用o替换revert_wrapper实际上是错误的。

答案 3 :(得分:1)

一旦你有了正常的(常规)迭代器, 使用标准库实现反向迭代器 辅助类模板std::reverse_iterator

#include <iterator>

class XX { 

    // your code

    typedef std::reverse_iterator<iterator> reverse_iterator;

    reverse_iterator rbegin() { return reverse_iterator{end()}; }
    reverse_iterator rend() { return reverse_iterator{begin()}; }

答案 4 :(得分:1)

查看完整代码lifo_stack.pop()会使迭代器失效,因此无法在范围内用于。您有未定义的行为

此外,对于堆栈使用范围没有多大意义。如果你可以迭代它的元素,那么它现在不是一个堆栈吗?堆栈具有访问最新插入元素的属性。

根据您的评论:

  

考虑慢慢地单独添加项目的情况,但是   希望尽快将它们从堆栈中转出。我没有   想要复制和调整pop()所需数组的开销   在那一刻触发。

我仍然认为ranged-for对于堆栈没有意义。

以下是我如何解决您的问题:

lifo_stack.disable_resizing(); // prevents resizing 
while (!lifo_stack.is_empty()
{
    lifo_stack.pop(); // maybe use the poped element
}
lifo_stack.enable_resizing(); // re-enables resizing and triggers a resize

如果您不需要弹出的元素并且只是想要移植堆栈,那么有一种更快的方法(基于您的类实现):

// empties the stack
void clear()
{
   delete[] array_ptr;
   array_ptr = new T[1];;
   max_size = 1;
   N = 0;
}

最后一个决赛如果您想使用现代C ++,请使用unique_ptr代替手动newdelete。它更容易,但最重要的是更安全。并阅读0/3/5的规则。

答案 5 :(得分:1)

请参阅TemplateRex here的优秀答案。我能够在没有包装类的情况下解决问题,因此我会回答我自己的问题。

这是我在example实现迭代器时发现的最有帮助的http://en.cppreference.com,您可以在找到问题的同一GitHub link找到更新的ResizingArrayStack代码。

template<typename T> class ResizingArrayStack {
public:

    //----- Begin reversed iteration section -----//
    // Please see the example here, (http://en.cppreference.com/w/cpp/iterator/iterator).
    // Member typedefs inherit from std::iterator.
    class stackIterator: public std::iterator<
                        std::input_iterator_tag,   // iterator_category
                        T,                         // value_type
                        T,                         // difference_type
                        const T*,                  // pointer
                        T                          // reference
                        >{
        int index = 0;
        T* it_ptr = nullptr;
    public:
        // Prefix ++, equal, unequal, and dereference operators are the minimum required for range based for-loops.
        stackIterator(int _index = 0, T* _it_ptr = nullptr) { index = _index; it_ptr = _it_ptr; }
        // Here is where we reverse the sequence.
        stackIterator& operator++() { --index; return *this; }
        bool operator==(stackIterator other) { return index == other.index; }
        bool operator!=(stackIterator other) { return !( *this == other ); }
        T operator*() { return it_ptr[index-1]; }
    };

    stackIterator begin() { return stackIterator(N, array_ptr); }
    stackIterator end() {
        N = 0;  // 'Empty' the array.
        max_size = 1;  // Don't waste time calling resize() now. 
        return stackIterator(0, array_ptr);
    }
    //----- End reversed iteration section -----//

private:
    // Allocate space for a traditional array on the heap.
    T* array_ptr = new T[1];
    // Keep track of the space allocated for the array, max_size * sizeof(T).
    int max_size = 1;
    // Keep track of the current number of items on the stack.
    int N = 0;

默认情况下,调用基于for循环的范围以反向(或LIFO)顺序迭代的代码。

// It's nice performance-wise to iterate in reverse without calling pop() or triggering a resize.
for ( auto i : lifo_stack) {
    cout << "Current loop iteration has i = " << i << endl;
}