我试图通过移植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;
我尝试切换上面的begin
和end
成员函数,但发现展开的for循环始终以++__begin
递增,即使__end
处于较低位置内存地址。我们怎样才能使i
反向循环(相对于堆栈的LIFO)?
如果有严重的错误或方面看起来过时,请随时评论我的代码风格。我希望与优秀的现代人保持一致。 C ++。
答案 0 :(得分:6)
如果要使用带有反向迭代器的range-for循环,可以使用存储范围的包装类Reverse
并返回与reverse_iterator
对应的begin
和{ {1}}
end
请注意,这使用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 << ',';
}
请注意,您必须小心将临时变量传递给此类适配器,我和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
的示例。
reverse
。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>&
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>
std::vector<int>&&
及其构造函数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
中的内容相同:我们将左值作为左值引用转发。
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)
来自左值,我们正在转发:
我们还可以在这里再看一件事:
按值存储的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
代替手动new
和delete
。它更容易,但最重要的是更安全。并阅读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;
}