将const容器引用作为参数传递时,如何转义const_iterator陷阱

时间:2009-10-12 22:45:29

标签: c++ stl iterator const

我通常更喜欢constness,但最近遇到了一个与const迭代器的难题,震撼我的const态度让我很烦恼:

MyList::const_iterator find( const MyList & list, int identifier )
{
    // do some stuff to find identifier
    return retConstItor; // has to be const_iterator because list is const
}

我试图在这里表达的想法当然是传入的列表不能/不会被更改,但是一旦我使列表引用const我就必须使用'const_iterator'然后阻止我<使用修改结果做任何事情(这是有道理的)。

那么解决方案是放弃制作传入的容器引用const,还是我错过了另一种可能性?

这一直是我对const的秘密保留:即使你正确使用它,它也可能产生在没有好/干净解决方案的情况下不应该出现的问题,尽管我认识到这更具体是一个问题。 const和迭代器的概念。

编辑:我非常清楚为什么你不能也不应该为const容器返回一个非const迭代器。我的问题是,虽然我想通过引用传入我的容器的编译时检查,我仍然想找到一种方法来传回一些东西的位置,并用它来修改列表的非const版本。正如其中一个答案所提到的那样,可以通过“提前”提取这个位置概念,但是凌乱/低效。

8 个答案:

答案 0 :(得分:10)

如果我理解你说的正确,你试图使用const向来电者表明你的功能不会修改集合,但是你想要调用者(可能有非const对集合的引用)能够使用您返回的迭代器修改集合。如果是这样,我认为没有一个干净的解决方案,除非容器提供了一个机制,用于将const交互器转换为非const交换器(我不知道容器是做什么的)这个)。您最好的选择可能是让您的函数采用非const引用。您也可能有两个函数重载,一个const和一个非const,因此对于只有const引用的调用者,他们会仍然可以使用你的功能。

答案 1 :(得分:6)

这不是陷阱;这是一个功能。 ( - ):

通常,您不能从const方法向数据返回非const“句柄”。例如,以下代码是非法的。

class Foo
   {
      public:
         int& getX() const {return x;}
      private:
         int x;
   };

如果它是合法的,那么你可以做这样的事情......

   int main()
   {
      const Foo f;
      f.getX() = 3; // changed value of const object
   }

STL的设计者遵循这个约定使用const-iterators。


在你的情况下,const会给你带来的是能够在const集合上调用它。在这种情况下,您不希望返回的迭代器可以修改。但是如果集合是非常量的,你确实希望它可以修改。因此,您可能需要两个接口:

MyList::const_iterator find( const MyList & list, int identifier )
{
    // do some stuff to find identifier
    return retConstItor; // has to be const_iterator because list is const
}

MyList::iterator find( MyList & list, int identifier )
{
    // do some stuff to find identifier
    return retItor; 
}

或者,您可以使用一个模板功能

完成所有操作
template<typename T>    
T find(T start, T end, int identifier);

如果输入迭代器是非const,那么它将返回非const迭代器,如果它们是const,则返回const_iterator。

答案 2 :(得分:3)

我对包装标准算法所做的工作有一个用于确定容器类型的元对象:

namespace detail
{
    template <class Range>
    struct Iterator
    {
        typedef typename Range::iterator type;
    };

    template <class Range>
    struct Iterator<const Range>
    {
        typedef typename Range::const_iterator type;
    };
}

这允许提供单个实现,例如find:

template <class Range, class Type>
typename detail::Iterator<Range>::type find(Range& range, const Type& value)
{
    return std::find(range.begin(), range.end(), value);
}

但是,这不允许用临时工具调用它(我想我可以忍受它)。

在任何情况下,要返回对容器的可修改引用,显然您无法保证您的函数对容器执行或不执行的操作。因此,这种崇高的原则确实被打破了:不要对它采取教条。

我认为const正确性更多的是为你的函数调用者提供的服务,而不是一些婴儿坐姿测量,它可以确保你的简单查找功能正确。


另一个问题是:如果我定义了一个跟随谓词,然后滥用标准的find_if算法将所有值增加到第一个值&gt; = 3,你会有什么感受:

bool inc_if_less_than_3(int& a)
{
    return a++ < 3;
}

(海湾合作委员会并没有阻止我,但我不知道是否有一些未定义的行为涉及迂腐。)

1)容器属于用户。由于允许通过谓词修改决不会损害算法,因此应由调用者决定如何使用它。

2)这太可怕了!更好地实现这样的find_if,以避免这种噩梦(最好的事情,因为,显然,你不能选择迭代器是否为const):

template <class Iter, class Pred>
Iter my_find_if(Iter first, Iter last, Pred fun)
{
    while (first != last 
       && !fun( const_cast<const typename std::iterator_traits<Iter>::value_type &>(*first)))
        ++first;
    return first;
}

答案 3 :(得分:2)

虽然我认为你的设计有点混乱(因为其他人指出迭代器允许容器中的更改,所以我没有看到你的函数真的是const),有一种方法可以从const_iterator中获取迭代器。效率取决于迭代器的类型。


#include <list>

int main()
{
  typedef std::list<int> IntList;
  typedef IntList::iterator Iter;
  typedef IntList::const_iterator ConstIter;

  IntList theList;
  ConstIter cit = function_returning_const_iter(theList);

  //Make non-const iter point to the same as the const iter.
  Iter it(theList.begin());
  std::advance(it, std::distance<ConstIter>(it, cit));

  return 0;
}

答案 4 :(得分:1)

不是试图保证使用const关键字不会更改列表,而是在这种情况下使用后置条件来保证它更好。换句话说,通过评论告诉用户列表不会被更改。

更好的方法是使用可以为迭代器或const_iterator实例化的模板:

template <typename II> // II models InputIterator
II find(II first, int identifier) {
  // do stuff
  return iterator;
}

当然,如果你要去那么麻烦,你也可以向用户公开MyList的迭代器并使用std :: find。

答案 5 :(得分:1)

如果您正在更改迭代器指示的数据,则表示您正在更改列表。

  

我想在这里表达的想法当然是传入的列表不能/不会被改变,但是一旦我使列表引用const我就必须使用'cons_iterator'然后阻止我做任何结果。

什么是“东西”?修改数据?这正在改变名单,这与你原来的意图相矛盾。如果列表是常量,则它(和“它”包括其数据)是常量。

如果你的函数要返回一个非const迭代器,它会创建一种修改列表的方法,因此列表不会是const。

答案 6 :(得分:1)

你正在以错误的方式思考你的设计。不要使用const参数来指示函数的作用 - 使用它们来描述参数。在这种情况下,find()不会更改列表并不重要。重要的是find()旨在与可修改列表一起使用。

如果希望find()函数返回非const迭代器,则它使调用者能够修改容器。接受一个const容器会是错误,因为这会为调用者提供一种隐藏的方法来移除容器的常量。

考虑:

// Your function
MyList::iterator find(const MyList& list, int identifier);

// Caller has a const list.
const MyList list = ...
// but your function lets them modify it.
*( find(list,3) ) = 5;

所以,你的函数应该采用非const参数:

MyList::iterator find(MyList& list, int identifier);

现在,当调用者尝试使用您的函数修改她的const列表时,她将收到编译错误。这是一个更好的结果。

答案 7 :(得分:0)

如果您要将非const访问器返回到容器,请将该函数设置为非const。您承认容器因副作用而被更改的可能性。

这是标准算法采用迭代器而不是容器的一个很好的理由,因此他们可以避免这个问题。