如何为类似STL的容器提供公共const迭代器和私有非const迭代器?

7
我有一个类,其中包括std::list,并希望为const_iterator提供公共的begin()和end(),并为普通iterator提供私有的begin()和end()。
然而,编译器看到了私有版本,并抱怨它是私有的,而不是使用公共的const版本。
我知道C++不会根据返回类型(在这种情况下是const_iterator和iterator)进行重载,因此它选择非const版本,因为我的对象不是const。
除了调用begin()之前将我的对象强制转换为const或者不重载名字begin之外,是否有一种实现这个目标的方法?
我认为这是一个已知的模式,人们在以前解决过这个问题,并且想跟随通常的解决方案。
class myObject {
public:
  void doSomethingConst() const;
};

class myContainer {
public:
  typedef std::list<myObject>::const_iterator const_iterator;
private:
  typedef std::list<myObject>::iterator iterator;

public:
  const_iterator begin() const { return _data.begin(); }
  const_iterator end()   const { return _data.end();   }
  void reorder();
private:
  iterator begin() { return _data.begin(); }
  iterator end()   { return _data.end();   }
private:
  std::list<myObject> _data;
};

void myFunction(myContainer &container) {
  myContainer::const_iterator itr = container.begin();
  myContainer::const_iterator endItr = container.end();
  for (; itr != endItr; ++itr) {
    const myObject &item = *itr;
    item.doSomethingConst();
  }
  container.reorder(); // Do something non-const on container itself.
}

编译器的错误大概是这样的:
../../src/example.h:447: error: `std::_List_iterator<myObject> myContainer::begin()' is private
caller.cpp:2393: error: within this context
../../src/example.h:450: error: `std::_List_iterator<myObject> myContainer::end()' is private
caller.cpp:2394: error: within this context

Thanks.

-William


据我所见,您的代码中没有继承,可能您应该更正“我正在私下派生一个类”的部分。 - Sad Developer
糟糕,编辑不同步了,感谢指出,现在已经纠正过来了。 - WilliamKF
1
如果您的非const begin和end是私有的容器,为什么要为它们提供私有函数?为什么不直接使用列表迭代器呢? - Khaled Alshaya
Arak,一个原因可能是如果begin()/end()被保护而成员变量仍然是私有的。 - WilliamKF
4个回答

5

从std::list派生是一个不好的想法(它不是设计用于派生的)。

使用类型为std::list的成员变量。

class myContainer
{
  std::list<myObject>   m_data;
  public:

    typedef std::list<myObject>::const_iterator myContainer::const_iterator;
  private:
    typedef std::list<myObject>::iterator myContainer::iterator;

  public:

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

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

  private:
    myContainer::iterator begin()
    {
      return m_data.begin();
    }

    myContainer::iterator end()
    {
      return m_data.end();
    }
};

已经更新了问题以反映这个好建议,尽管主要问题仍然存在。似乎唯一的解决方案是将私有变量重命名或在调用begin()/end()之前进行static_cast转换为const。 - WilliamKF

4
你需要更改私有的begin和end的名称。编译器不能仅凭返回类型区分它们。
这种方法对我有效:注意_begin_和_end_名称。
#include <list>


class myObject {};

class myContainer : private std::list<myObject> {
public:
    typedef std::list<myObject>::const_iterator const_iterator;
private:
    typedef std::list<myObject>::iterator iterator;

public:
  myContainer::const_iterator begin() const {
    return std::list<myObject>::begin();
  }
  myContainer::const_iterator end() const {
    return std::list<myObject>::end();
  }
private:
  myContainer::iterator _begin() {
    return std::list<myObject>::begin();
  }
  myContainer::iterator _end() {
    return std::list<myObject>::end();
  }
};

void myFunction(myContainer &container) {
  myContainer::const_iterator aItr = container.begin();
  myContainer::const_iterator aEndItr = container.end();
  for (; aItr != aEndItr; ++aItr) {
    const myObject &item = *aItr;
    // Do something const on container's contents.
  }
}

int main(){
    myContainer m;
    myFunction(m);
}

1
尽管在这里重命名可能会解决问题,但如果您想将对象放入期望 begin()end() 的通用函数中,仍然会遇到麻烦... - xtofl
似乎这是唯一可行的选择,除了为每个调用者执行static_cast<const myContainer>(container).begin()/end()。 - WilliamKF
@Emilie Cormier 我不知道那个。谢谢!在Python中,使用前导_来命名私有成员是一种常见的习惯用法。 @xtofl,如果这些函数是私有的,除了类本身之外,没有人可以使用它,你说的通用函数是什么?有例子吗? - fabrizioM

4

我认为你唯一的选择是重命名私有方法(如果你需要它们的话)。

此外,我认为你应该重新命名typedefs:

class MyContainer
{
public:
     typedef std::list<Object>::const_iterator iterator;
     typedef iterator const_iterator;

     const_iterator begin() const;
     const_iterator end() const;

private:
     typedef std::list<Object>::iterator _iterator;
     _iterator _begin();
     _iterator _end();
     ...
};

容器应该typedef iteratorconst_iterator。一个接受非const实例的通用函数可能希望使用iterator typedef - 即使它不打算修改元素。(例如,BOOST_FOREACH。)

就const正确性而言,这样做是可以的,因为如果通用函数实际上尝试修改对象,真正的迭代器类型(即const_iterator)不会让它这样做。

作为测试,以下内容应该能够与您的容器一起编译:

int main()
{
    myContainer m;
    BOOST_FOREACH(const myObject& o, m) 
    {}
}

请注意,m并非const,但我们仅尝试获取所包含类型的const引用,因此应该是允许的。

糟糕,他比我早几秒钟发了帖子! - Potatoswatter
请参阅https://dev59.com/KHVC5IYBdhLWcg3woSxW。 - Emile Cormier
@Emile:据我所知,使用一个下划线后跟一个小写字符是可以的。但个人而言,我确实不会使用这种风格。 - UncleBens

1

你可能想要将你的方法Myfunction的签名更改为这样:

void myFunction(const myContainer &container) 

因为const方法只会在const对象上调用。目前发生的情况是,您正在尝试调用一个非const方法,而在您的情况下该方法是私有的。


但在某些情况下,这还不够,已更新测试用例以反映此情况。 - WilliamKF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接