VS2008 到 VS2010 STL关联容器set 的一点变化
最近在把项目从vs2008移植vs2010的时候, 发现在vs2008下编译通过的代码却在vs2010编译时出现错误。下面是代码的一个简化的示例:
vector<int> v(10, 1);
set<int> s(v.begin(), v.end());
set<int>::iterator i = s.begin();
(*i) = 0; // 这行赋值导致VS2010编译失败
这段代码在VS2008中顺利通过编译,但在VS2010中出现以下错误:
error C3892: 'i' : you cannot assign to a variable that is const
这说明编译器认为i是一个const变量, 可我们明明定义i为set<int>::iterator而不是set<int>::const_iterator, 是哪里出了问题?难道VS2010做了一些特殊处理?可这种特殊处理为什么要导致不能向后兼容呢?要想弄清楚怎么回事, 最直接的方法就是看看VS2010中set的源代码,其中有这么一段:
typedef _Tree_const_iterator<_Mybase> const_iterator;
typedef typename _STD tr1::conditional<
_STD tr1::is_same<key_type, value_type>::value, const_iterator,
_Tree_iterator<_Mybase> >::type iterator;
上面的代码说明const_iterator依然具有const属性,但对于iterator的定义要依赖于key_type和value_type是否是同一种类型,而关联容器set就正是key和value是同一种类型的容器,所以上面的定义相当于:
typedef const_iterator iterator;
所以对于set来说iterator和const_iterator都具有const属性,不能修改set容器中的值。这就解释了为什么一开始的复值语句为什么会失败。
接下来的问题是为什么VS2010要做这样不向后兼容的改变?为了更加安全?我查阅了C++ 98 和C++ 2003的标准, 其中关于关联容器iterator的描述并没有明确指出set的iterator必须是const的。之后在C++标准定义网站上在标准库的defect report中找到了答案, defect 103 描述了为什么要将set的iterator改为const的。下面是摘录的一部分:
103. set::iterator is required to be modifiable, but this allows modification of keys
Section: 23.2.4 [associative.reqmts] Status: CD1 Submitter: AFNOR Opened: 1998-10-07 Last modified: 2008-09-26
Proposed resolution:
At the end of paragraph 5: "Keys in an associative container are immutable."
At the end of paragraph 6: "For associative containers where the value type is the same as the key type, both iterator and const_iterator are constant iterators. It is unspecified whether or not iterator and const_iterator are the same type."
这样就避免了由于程序员的疏忽而更改了set的key,从而导致了set中的元素不再有序。由此看来VS2010是符合最新的C++标准的。其实VS2008也是符合C++标准的,因为VS2008当时标准并没有明确说明set的key能否修改,所以VS2008两种都支持,它定义了一个宏_HAS_IMMUTABLE_SETS,缺省情况下这个宏被定义为0,一次set的key是可以被修改的,如果想得到一个key不能被修改的set,只需要定义宏_HAS_IMMUTABLE_SETS为1即可。这也说明了VS2008的态度,认可这种key可修改的set在多数情况下被使用,因为一般每人会去自定义随编译器分发的STL库。
最后,我想说的是在Effective STL的第22条中,scott指出切勿直接修改set或multiset中的键。之所以这样是因为会导致不可移植的代码,因为有的编译器允许修改key,而有的就不允许(比如gcc),并且其中还提到了怎样修改key才是安全的做法。现在C++标准已经明确的指出set中的key是immutable的,所以这个条款估计在下一版的时候会有些调整,不过里面提到的一些方法,如用强制转换来达到修改key的目的和其他的建议仍然有效,有兴趣的朋友可以看看。
参考文章:
1. Effective STL 第22条
2. http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#103