如何从std::列表中实现0(1)擦除
How to achieve O(1) erasure from a std::list
问题是使用std::list
实现0(1)擦除列表项的推荐方法是什么?
通常,当我选择一个双链表时,我希望能够在O(1)时间内从一个列表中移除一个元素,然后在O(1)时间内将其移动到另一个列表中。如果元素有自己的prev
和next
指针,则没有真正的技巧来完成这项工作。如果链表是双链循环链表,则删除该链表并不一定需要知道包含该项的链表。
根据迭代器失效规则,std::list
迭代器是非常持久的。所以,对我来说,在我自己的项目上使用std::list
时,我想要的行为似乎是在我的类和包含列表中隐藏一个迭代器。
class Item {
typedef std::shared_ptr<Item> Ptr;
struct Ref {
std::list<Ptr>::iterator iter_;
std::list<Ptr> *list_;
};
Ref ref_;
//...
};
这有一个缺点,我需要创建我自己的std::list
的装饰版本,知道何时将项目添加到列表中更新ref_
。我想不出一种不需要嵌入式迭代器的方法,因为没有嵌入式迭代器意味着擦除将首先引发O(n)查找操作。
用std::list
获得O(1)擦除的推荐方法是什么?或者,是否有更好的方法来实现这个目标?
在过去,我通过实现自己的列表数据结构来实现这一点,其中列表中的项有自己的next和prev指针。管理这些指针是很自然的,因为它们是列表操作本身固有的(我的列表实现的API调整了指针)。如果我想使用STL,实现这一目标的最佳方法是什么?我提供了嵌入迭代器的稻草人建议。有没有更好的方法?
如果需要一个具体的用例,考虑一个计时器实现。创建计时器后,将其放入适当的列表中。如果取消了,则需要有效地删除它。(这个特殊的例子可以通过标记而不是删除来解决,但这是实现取消的有效方法。)其他用例可根据要求提供。
我探索的另一种选择是融合std::list
和std::unordered_map
,为指针类型创建一个专门的列表。这是更重量级的(因为哈希表),但是提供了一个在接口级别非常接近标准容器的容器,并且给我O(1)个列表元素擦除。稻草人提案中唯一缺少的特性是指向当前包含该项的列表的指针。我已经在CodeReview上发布了当前的实现以征求意见。
std::list::erase
保证为0(1)。
从标准列表中删除元素的方法并不多。(std::list::remove
和朋友不做完全相同的事情,所以他们不计算)。
如果要从标准列表中擦除,则需要迭代器和列表本身。你好像已经有了。没有太多的自由去做不同的事情。我会将列表包含与对象分开,不像您所做的那样,因为为什么要创建一次只能在一个列表中的对象呢?对我来说这是不必要的人为限制。但无论你的设计有什么动力。
也许您可以重新设计您的接口,以提供迭代器而不是原始对象?在计时器的例子中:
class Timer {
// ...
};
typedef std::list<Timer>::iterator TimerRef;
class Timers {
public:
TimerRef createTimer(long time);
void cancelTimer(TimerRef ref);
private:
std::list<Timer> timers;
};
当然,而不是
timer.cancel();
类的用户现在必须说
timers.cancelTimer(timerRef);
但根据您的用例,这可能不是问题。
更新:在列表之间移动计时器:
class Timers {
public:
Timer removeTimer(TimerRef ref);
void addTimer(Timer const &timer);
// ...
};
用法:
timers2.addTimer(timers1.removeTimer(timerRef));
无可否认,这有点麻烦,但其他选择也是如此。
不可能从std::list中进行0(1)擦除。
您可以考虑使用intrusive list
,其中列表节点直接嵌入到结构中,就像您已经做过的那样。
你可以使用boost:: intrusion或滚动你自己的,也看看这个
这是一个使用嵌入式iterator
的"完整"解决方案。使用一些私有特征来帮助减少类中的混乱:
template <typename T> class List;
template <typename T>
class ListTraits {
protected:
typedef std::list<std::shared_ptr<T>> Impl;
typedef typename Impl::iterator Iterator;
typedef typename Impl::const_iterator ConstIterator;
typedef typename Impl::reverse_iterator Rotareti;
typedef typename Impl::const_reverse_iterator ConstRotareti;
typedef std::map<const List<T> *, typename Impl::iterator> Ref;
};
如所示,列表实现将使用std::list
,但底层值类型将是std::shared_ptr
。我所追求的是允许T
的实例有效地派生其自己的iterator
,以实现O(1)擦除。这是通过使用Ref
来记忆元素插入列表后的迭代器来实现的。
template <typename T>
class List : public ListTraits<T> {
template <typename ITER> class IteratorT;
typedef ListTraits<T> Traits;
typename Traits::Impl impl_;
public:
typedef typename Traits::Impl::size_type size_type;
typedef typename Traits::Impl::value_type pointer;
typedef pointer value_type;
typedef IteratorT<typename Traits::Iterator> iterator;
typedef IteratorT<typename Traits::ConstIterator> const_iterator;
typedef IteratorT<typename Traits::Rotareti> reverse_iterator;
typedef IteratorT<typename Traits::ConstRotareti> const_reverse_iterator;
class Item;
~List () { while (!empty()) pop_front(); }
size_type size () const { return impl_.size(); }
bool empty () const { return impl_.empty(); }
iterator begin () { return impl_.begin(); }
iterator end () { return impl_.end(); }
const_iterator begin () const { return impl_.begin(); }
const_iterator end () const { return impl_.end(); }
reverse_iterator rbegin () { return impl_.rbegin(); }
reverse_iterator rend () { return impl_.rend(); }
const_reverse_iterator rbegin () const { return impl_.rbegin(); }
const_reverse_iterator rend () const { return impl_.rend(); }
pointer front () const { return !empty() ? impl_.front() : pointer(); }
pointer back () const { return !empty() ? impl_.back() : pointer(); }
void push_front (const pointer &e);
void pop_front ();
void push_back (const pointer &e);
void pop_back ();
void erase (const pointer &e);
bool contains (const pointer &e) const;
};
这个List
主要遵循一个类似队列的接口。但是,一个项目可以从列表中的任何位置移除。简单的函数大多只是委托给底层的std::list
。但是push_*()
和pop_*()
方法也记忆了iterator
。
template <typename T>
template <typename ITER>
class List<T>::IteratorT : public ITER {
friend class List<T>;
ITER iter_;
IteratorT (ITER i) : iter_(i) {}
public:
IteratorT () : iter_() {}
IteratorT & operator ++ () { ++iter_; return *this; }
IteratorT & operator -- () { --iter_; return *this; }
IteratorT operator ++ (int) { return iter_++; }
IteratorT operator -- (int) { return iter_--; }
bool operator == (const IteratorT &x) const { return iter_ == x.iter_; }
bool operator != (const IteratorT &x) const { return iter_ != x.iter_; }
T & operator * () const { return **iter_; }
pointer operator -> () const { return *iter_; }
};
这是用于定义List
的迭代器类型的helper模板的实现。它的不同之处在于,*
和->
操作符的定义方式使迭代器的行为像T *
而不是std::shared_ptr<T> *
(这是底层迭代器通常会做的)。
template <typename T>
class List<T>::Item {
friend class List<T>;
mutable typename Traits::Ref ref_;
};
从List<T>::Item
派生的T
类型可以添加到List<T>
类型。此基类包含Ref
实例,用于在将元素添加到列表中时记忆迭代器。
template <typename T>
inline void List<T>::push_front (const pointer &e) {
const Item &item = *e;
typename Traits::Ref::iterator i = item.ref_.find(this);
if (i == item.ref_.end()) {
item.ref_[this] = impl_.insert(impl_.begin(), e);
} else if (front() != e) {
impl_.erase(i->second);
i->second = impl_.insert(impl_.begin(), e);
}
}
template <typename T>
inline void List<T>::pop_front () {
if (!empty()) {
const Item &item = *front();
item.ref_.erase(this);
impl_.pop_front();
}
}
这段代码说明了记忆是如何执行的。在执行push_front()
时,首先检查该项是否已经包含。如果不是,则插入它,并将结果迭代器添加到ref_
对象中。否则,如果它不是前面的元素,则删除该元素并将其重新插入到前面,并更新记忆迭代器。pop_front()
移除记忆迭代器,然后在std::list
上调用pop_front()
。
template <typename T>
inline void List<T>::push_back (const pointer &e) {
const Item &item = *e;
typename Traits::Ref::iterator i = item.ref_.find(this);
if (i == item.ref_.end()) {
item.ref_[this] = impl_.insert(impl_.end(), e);
} else if (back() != e) {
impl_.erase(i->second);
i->second = impl_.insert(impl_.end(), e);
}
}
template <typename T>
inline void List<T>::pop_back () {
if (!empty()) {
const Item &item = *back();
item.ref_.erase(this);
impl_.pop_back();
}
}
push_back()
和pop_back()
与push_front()
和pop_front()
相似。
template <typename T>
inline void List<T>::erase (const pointer &e) {
const Item &item = *e;
typename Traits::Ref::iterator i = item.ref_.find(this);
if (i != item.ref_.end()) {
item.ref_.erase(i);
impl_.erase(i->second);
}
}
erase()
例程获取记忆迭代器,并使用它执行擦除操作。
template <typename T>
inline bool List<T>::contains (const pointer &e) const {
const Item &item = *e;
typename Traits::Ref::iterator i = item.ref_.find(this);
return i != item.ref_.end();
}
由于项目在很多方面都是它自己的迭代器,因此在List
的这个版本中不需要find()
方法。但是,代替它的是contains()
方法,该方法用于查看元素是否为列表的成员。
现在,给出的解决方案使用std::map
将列表实例关联到迭代器。如果一个项目同时属于的列表数量相对较少,这就保持了O(1)的精神。
我将尝试我的手在boost::intrusive
版本下。
可怕的事实:虽然链表是一个强大的结构,std::list
不能充分利用它的能力。
你不能只使用迭代器从std::list
中删除对象,因为list必须释放节点,并且你必须知道分配内存的分配器在哪里(提示:它在列表中)。
与标准链表相比,侵入式容器有很多优点,比如自我意识;-),按值存储多态对象的能力,它们使列表技巧(比如在多个列表中拥有单个对象)变得可行。既然您不直接使用std::list
,那么您可以完全放弃使用std::list
并使用第三方容器,或者使用您自己的容器。
(同样,您的解决方案也是侵入性的,因为您的类型必须从List<T>::Item
派生,这对类型施加了std::list
没有的某些要求)
- 引用一个已擦除类型(void*)的指针
- 擦除while循环中迭代的元素
- 在运行时处理类型擦除的数据-如何不重新发明轮子
- C++擦除(如果存在)
- 在映射擦除c++期间执行循环的次数
- 为什么擦除方法会影响结束方法
- C++ 字符串类擦除成员函数的时空复杂性
- 类型擦除的std::function与虚拟函数调用的开销
- C++14 中unordered_map矢量和擦除删除成语的奇怪行为
- free():实现矢量擦除()时的指针无效
- 为什么这种类型的擦除实现(简化的 boost:any)会出现分段错误
- 无法使用迭代器擦除矢量元素,矢量元素实现移动构造函数
- 为我的类实现擦除功能
- 如何实现哈希表的擦除函数
- 如何实现像 std::function 这样的自毁类型擦除类
- 向量类中擦除函数的正确实现是什么
- 实现任意类型擦除的小缓冲区优化的简单方法(如std::function.)
- SFINAE使用模板、专门化和实现类型擦除
- 如何从std::列表中实现0(1)擦除
- 如何实现擦除方法