我通常更喜欢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版本。正如其中一个答案所提到的那样,可以通过“提前”提取这个位置概念,但是凌乱/低效。
答案 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。您承认容器因副作用而被更改的可能性。
这是标准算法采用迭代器而不是容器的一个很好的理由,因此他们可以避免这个问题。