【问题标题】:Overload operator++ for encapsulated standard container封装标准容器的重载运算符++
【发布时间】:2018-03-14 16:07:51
【问题描述】:

我封装了一个 std::list 以使其在迭代时可以安全地迭代,可能会将内容标记为“无效”,并且在迭代中会跳过“无效”内容。具体来说,在迭代过程中,当前对象可以安排自己或其他对象从列表中删除,并将这些对象标记为无效。然后定期清除列表中的无效对象。

如何定义增量运算符以使基于范围的 for 循环正常工作?这是我的课程:

template <typename T> class DeferredCleanupList
{
public:
    DeferredCleanupList() {
        (void)static_cast<Valid *>((T)0);
    }
    virtual ~DeferredCleanupList() {}

    typedef typename std::list<T>::iterator iterator;

    iterator begin() {
        iterator it = container.begin();

        if ((*it)->valid())
            return it;

        return next(it);
    }
    iterator next(iterator it) {
        do {
            ++it;
        }
        while (it != end() && !(*it)->valid());

        return it;
    }
    iterator end() { return container.end(); }

//    to be implemented:
//    typedef typename std::list<T>::const_iterator const_iterator ;
//    const_iterator cbegin() const { return container.cbegin(); }
//    const_iterator cend() const { return container.cend(); }
//    const_iterator cnext() const { ??? }

    size_t size() const { return container.size(); }

    void add(T *ptr) { container.push_front(ptr); }
    void remove(T *ptr) { ptr->invalidate(); }

    // called occasionally
    void delete_invalid() {
        for (auto it = container.begin(); it != container.end(); ) {
            auto ptr = *it;
            if (ptr->valid())
                ++it;
            else {
                delete ptr;
                it = container.erase(it);
            }
        }
    }

private:
    DeferredCleanupList(const DeferredCleanupList&);
    DeferredCleanupList& operator=(const DeferredCleanupList&);

    std::list<T> container;
};

我当前的测试用例是这样的:

int main() {
    class D : public Valid {};

    DeferredCleanupList<D *> list;

    for (auto it = list.begin(); it != list.end(); it = list.next(it)); // works
    for (auto ptr : list); // iterates, but doesn't call list.next(it)
}

编辑: 经过反复试验,我根据 cmets 中的建议编写了这个迭代器包装器:

template <typename T> class DeferredCleanupList
{
public:
    class iterator {
    public:
        iterator(typename std::list<T>::iterator it, DeferredCleanupList<T>& ls) : it(it), list(ls) {}

        iterator& operator=(const iterator& rhs) { it = rhs; return *this; }
        iterator& operator++() {
            do {
                ++it;
            }
            while (it != list.end().it && !(*it)->valid());

            return *this;
        }
        friend bool operator==(const iterator& lhs, const iterator& rhs) { return lhs.it == rhs.it; }
        friend bool operator!=(const iterator& lhs, const iterator& rhs) { return !(lhs == rhs); }

        T& operator*() { return *it; }

    private:
        typename std::list<T>::iterator it;
        DeferredCleanupList& list;
    };

    iterator begin() {
        iterator it = iterator(container.begin(), *this);

        if (it == end() || (*it)->valid())
            return it;

        return ++it;
    }

    iterator end() { return iterator(container.end(), *this); }
}

它似乎在我扔给它的所有测试用例中都能完美运行。这种方法我是否遗漏了任何明显的东西?有没有更优雅的解决方案?

【问题讨论】:

  • 使用typedef typename std::list&lt;T&gt;::iterator iterator; 无法覆盖预增量运算符,因为编译器无法区分容器的迭代器和标准列表迭代器。您需要创建一个在内部存储列表迭代器的包装类,然后重新定义预增量运算符 - 作为新类的方法,它需要看起来像 iterator&amp; operator++()
  • 另外,顺便提一下,如果您尝试在空容器上执行 begin() 的当前实现可能会崩溃(即底层 std::list&lt;T&gt; 是空的)。
  • 很好地抓住了 begin() 崩溃。我根据您的建议使用我提出的解决方案更新了我的问题。它工作得很好,我只是希望有一个更简单的解决方案。
  • Boost 提供了一个boost::iterators::filter_iterator 模板类,它做了很多你在解决方案中所做的事情,只是更普遍。使用它而不是自己滚动可能会使事情变得更简单,更容易阅读。

标签: c++ containers


【解决方案1】:

你不能在容器类中做到这一点。您应该实现自己的迭代器并在它的前缀增量运算符中实现您想要的行为。您可以通过多种方式完成此操作,包括从 STL继承一种或为 std::list 迭代器创建自己的包装器。

实际上,要在迭代器中实现您想要的行为,您需要向您的自定义迭代器类提供容器的结束迭代器

我不建议你实现自己的迭代器,除非你的设计真的很好用。不必要的并发症几乎总是会导致更多不必要的并发症。

我已经修改了您的代码,因此它实际上编译并实现了 std::list 迭代器包装器以按照您的计划运行。

#include <iostream>
#include <list>
#include <iterator>

using namespace std;

class Valid {};

template<typename _Tp>
struct myIteratorWrapper
{

  typedef myIteratorWrapper<_Tp>         _Self;
  typedef _Tp               value_type;
  typedef _Tp*                 pointer;
  typedef _Tp&               reference;

  typedef typename std::list<_Tp>::iterator listIterator;

  listIterator it;
  listIterator itEnd;

  myIteratorWrapper(const listIterator& listIterArg) _GLIBCXX_NOEXCEPT
    : it(listIterArg)
  {}

  myIteratorWrapper(const listIterator& itBegin,
            const listIterator& itEnd) _GLIBCXX_NOEXCEPT
    : it(itBegin), itEnd(itEnd)
  {}

  reference
  operator*() const _GLIBCXX_NOEXCEPT
  { return *it; }

  pointer
  operator->() const _GLIBCXX_NOEXCEPT
  { return &(*it); }

  /* Change logic of this method as you wish, but keep the signature */ 
  _Self&
  operator++() _GLIBCXX_NOEXCEPT
  {
    do
    {
      ++it;
    }while (it != itEnd && !(*it)->valid());

    return *this;
  }

  bool
  operator==(const _Self& __x) const _GLIBCXX_NOEXCEPT
  { return it == __x.it; }

  bool
  operator!=(const _Self& __x) const _GLIBCXX_NOEXCEPT
  { return it != __x.it; }
};

template <typename T> class DeferredCleanupList
{
public:
  DeferredCleanupList() {
    (void)static_cast<Valid *>((T)0);
  }
  virtual ~DeferredCleanupList() {}

  typedef myIteratorWrapper<T> iterator;

  iterator begin() {
    iterator it(container.begin(), container.end());
    return it;
  }

  iterator next(iterator it) {       
    return ++it;
  }

  iterator end() { return container.end(); }

  size_t size() const { return container.size(); }

  void add(T ptr) { container.push_front(ptr); }
  void remove(T ptr) { ptr->invalidate(); }

  // called occasionally
  void delete_invalid() {
    for (auto it = container.begin(); it != container.end(); ) {
      auto ptr = *it;
      if (ptr->valid())
    ++it;
      else {
    delete ptr;
    it = container.erase(it);
      }
    }
  }

private:
  DeferredCleanupList(const DeferredCleanupList&);
  DeferredCleanupList& operator=(const DeferredCleanupList&);

  std::list<T> container;
};

class D : public Valid {
  bool isValid;
  std::string myName;
public:

  D(std::string myName, bool arg = false)
    : myName(myName), isValid(arg) {}

  bool valid() const
  { return isValid; }

  const std::string& whoAmI() const
  { return myName; }
};

int main() {
  D o1("o1", true);
  D o2("o2");
  D o3("o3", true);
  D o4("o4");
  D o5("o5", true);

  DeferredCleanupList<D *> list;
  list.add(&o1);
  list.add(&o2);
  list.add(&o3);
  list.add(&o4);
  list.add(&o5);

  for (auto ptr : list)
    {
      std::cout << ptr->whoAmI() << std::endl;
    }
}

输出:

o5
o3
o1

【讨论】:

  • 感谢您的回答。我将继续接受这一点,因为它完全回答了如何实施解决方案的问题。然而,我已经在我的编辑中提出了一个解决方案,据我所知,它与此非常相似。你介意指出我的解决方案中你所涵盖的任何重大缺陷吗?
【解决方案2】:

来自http://en.cppreference.com/w/cpp/language/range-for

上面的语法产生的代码等价于下面的 (__range, __begin 和 __end 仅用于说明):

{
    auto && __range = range_expression ; 
    for (auto __begin = begin_expr, __end = end_expr; 
            __begin != __end; ++__begin) { 
        range_declaration = *__begin; 
    loop_statement 
    } 
}

即您必须定义预增量运算符。

【讨论】:

  • 是的,但我如何在课堂上做到这一点?更重要的是,如何从 operator++ 定义中调用默认的预增量运算符?我会在问题中提出我正在尝试的内容。
  • 后自增运算符返回原来的副本;然后增加自己。这意味着它不会返回引用而是返回值。然而,预增量将自身增加一,然后返回对自身的引用。它有签名`T&T::operator++();`
猜你喜欢
  • 2021-01-06
  • 1970-01-01
  • 2012-03-08
  • 1970-01-01
  • 2017-03-30
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 2014-04-19
相关资源
最近更新 更多