【问题标题】:How to adapt a set iterator to behave like a map iterator?如何调整集合迭代器以使其表现得像地图迭代器?
【发布时间】:2010-11-12 14:10:42
【问题描述】:

我有一个 Foo 类,它包含一个 map 并提供 begin()end() 函数来迭代它:

class Foo {
  typedef std::map<int, double> Container;
  typedef Container::const_iterator const_iterator;
  Container c_;
 public:
  const_iterator begin() const { return c_.begin(); }
  const_iterator end() const { return c_.end(); }
  void insert(int i, double d) { c_[i] = d; }
  // ...

};

现在我想在内部将其从 std::map&lt;int, double&gt; 更改为 std::set&lt;int&gt;,但我不想破坏任何客户端代码。

所以insert 函数中的double d 现在将被忽略。以下代码应该仍然有效,其中it-&gt;second 现在将始终为0.0

Foo foo;
for(Foo::const_iterator it = foo.begin(); it != foo.end(); ++it) {
  std::cout << it->first << " " << it->second << std::endl;
}

如何在Foo 类中进行这些更改?

换句话说,我怎样才能提供一个Foo::const_iterator 来适应新的内部std::set&lt;int&gt;::const_iterator 以使其表现得像旧的std::map&lt;int,double&gt;::const_iterator

更新:我想摆脱map 的原因是内存效率。我有数百万个 Foo 实例,无法在其中存储 double 值。

【问题讨论】:

  • 老实说,这听起来是个非常糟糕的主意。当语义完全改变时保持合约兼容是没有意义的。
  • @KonradRudolph C++ 从第一天就开始这样做了。
  • @wilhelm:确实如此。但不是这样做的理由。
  • @Konrad 是的。我在开玩笑:在 C++ 中,它是(现在有问题的?)设计的一部分。这是一个明显的后果,我们可能可以避免。向后兼容性是个难题,只有在绝对必须要做的时候才要做。

标签: c++ stl iterator encapsulation


【解决方案1】:

会使用

std::set<std::pair<int, double> >

这种可比性还不够?

如果您始终无法编写自己的迭代器,该迭代器包装 std::list 迭代器并提供 firstsecond 成员。基本上,您的 operator++ 将在真正的迭代器等上调用 operator++ ,并且取消引用运算符可以返回临时 std::pair (按值)或对存在于迭代器本身内的 std::pair 的引用(如果您的遗留代码可以处理)。

更新,稍微做作的示例,可能会根据您的情况起作用:

#include <iostream>
#include <set>

class Foo {
  typedef std::set<int> Container;
  typedef Container::const_iterator legacy_iterator;
  Container c_;

  // legacy iterator doesn't have a virtual destructor (probably?), shouldn't
  // be a problem for sane usage though
  class compat_iterator : public legacy_iterator {
  public:
     compat_iterator(const legacy_iterator& it) : legacy_iterator(it) {
     }

     const std::pair<int,double> *operator->() const {
        static std::pair<int,double> value;
        value = std::make_pair(**this, 0.0);
        // Not meeting the usual semantics!
        return &value;
     }
  };
 public:
  typedef compat_iterator const_iterator;

  const_iterator begin() const { return c_.begin(); }
  const_iterator end() const { return c_.end(); }

};



int main() {

  Foo foo;
  for(Foo::const_iterator it = foo.begin(); it != foo.end(); ++it) {
     std::cout << it->first << " " << it->second << std::endl;
  }

}

【讨论】:

  • 不,我应该这样说:我现在需要set 的原因是我想节省内存。我有数百万个 Foo 实例,无法再存储双精度。
  • 我已经更新了我的答案以添加一个示例,该示例仅以最少的额外开销实现兼容性。当然,现在获取其中任何一个成员的地址是个坏主意,但如果遗留代码不依赖它,那么它可能会解决您的问题。
【解决方案2】:

这样的事情怎么样?

#include <iostream>
#include <map>
#include <set>

struct Funky
{
    int first;
    static const double second;

    Funky(int i)
    :   first(i)
    {}
};

const double Funky::second = 0.0;

bool operator<(const Funky& lhs, const Funky& rhs)
{
    return lhs.first < rhs.first;
}

class Foo
{
private:
    //std::map<int,double> m_data;
    std::set<Funky> m_data;
public:
    //typedef std::map<int,double>::const_iterator const_iterator;
    typedef std::set<Funky>::const_iterator const_iterator;

    const_iterator begin() const
    {
        return m_data.begin();
    }

    const_iterator end() const
    {
        return m_data.end();
    }

    void insert(int i, double d)
    {
        //m_data.insert(std::make_pair(i, d));
        m_data.insert(i);
    }
};

int main()
{
    Foo foo;
    foo.insert(23, 9.0);
    for(Foo::const_iterator it=foo.begin(), iend=foo.end(); it!=iend; ++it)
    {
        std::cout << it->first << ' ' << it->second << '\n';
    }
    return 0;
}

【讨论】:

  • 我应该指出的一点是,任何试图改变it-&gt;second 的东西现在都会失败——所以你可能想要摆脱 const。或者,您可能希望人们知道发生了什么......
  • (我确实认为做这种事情的整个想法是相当不明智的,为了记录......)
  • 我不认为重新定义operator&lt;是必要的,默认就足够了。
  • AFAICS 这是完全必要的(g++ 也同意)——你为什么认为它不是?
  • Comeau 也同意,顺便说一句。
【解决方案3】:

大概是这样的

operator int()(const std::pair<int, double>& p) const {
    return p.first;
}

也许在某个包装内?

【讨论】:

    【解决方案4】:

    也许您可以定义一个实现firstsecondfake_pair 类,并在Foo 中放置一个set&lt;fake_pair&gt;

    【讨论】:

      【解决方案5】:

      你不能,不完全是。问题是你正在改变你的界面,这总是会破坏你的客户。我建议您创建两个具有新行为的新函数 newBegin 和 newEnd(或类似函数)。您的旧界面保持不变,但将其标记为已折旧。这个旧接口的实现可以使用其他人描述的解决方法之一。

      【讨论】:

        猜你喜欢
        • 2012-03-02
        • 1970-01-01
        • 2016-03-30
        • 2011-11-10
        • 1970-01-01
        • 2012-07-07
        • 2016-12-24
        • 1970-01-01
        • 2014-12-27
        相关资源
        最近更新 更多