为什么VC++允许在STL容器中使用const类型?

12

STL容器要求存储的值是可复制构造和可赋值的。对于任何T类型,const T显然都不是可赋值类型,但我尝试使用它(只是出于好奇),发现它可以编译并且行为像可赋值类型。

vector<const int> v(1);
v[0] = 17;

这段代码在 Visual Studio 2008 中顺利运行,将 v[0] 赋值为 17。


1
不仅如此——VS 2010 还允许您对 v[0] 赋新值。试试吧! - Kristian D'Amato
1
我很好奇VC对于typeid(vector<const int>::value_type).name()的输出结果是什么(并且可以将其与typeid(vector<int>::value_type).name()进行比较或解码)。 - Fred Nurk
同一个:int - Kristian D'Amato
5
在VC的标准库实现中,vector<T>::value_type是通过allocator<T>::value_type获得的。对于const T,allocator<T>::value_type有部分特化,只需删除const即可: template<class _Ty> struct _Allocator_base<const _Ty>{ typedef _Ty value_type;}; - Eugen Constantin Dinca
2个回答

12

正如其他人所建议的那样,这不是实现中的一个错误。

违反C ++标准库设施的要求并不会使您的程序无效,它会产生未定义的行为。

您已经违反了容器中存储的值类型必须是可复制构造和可分配的要求(显然const类型是不可分配的),因此您的程序表现出未定义的行为。

C ++标准中适用的语言可以在C ++03 17.4.3.6 [lib.res.on.functions]中找到:

在某些情况下(替换函数、处理程序函数、对用于实例化标准库模板组件的类型进行的操作),C ++标准库依赖于由C ++程序提供的组件。如果这些组件不满足其要求,则标准对实现不提出任何要求。

特别地,在以下情况下,效果是未定义的:

...

  • 对于用作实例化模板组件的模板参数的类型,如果类型的操作未实现适用的要求子句的语义。

Visual C ++标准库实现可以对此代码执行任何操作,包括静默删除或忽略const限定符,仍然符合标准。


3
依我之见,这里删除默许的资格实在是非常难看。 - James McNellis
资格限定符的删除可能会破坏反常但仍符合标准的代码。您可以编写一个具有const限定符的复制赋值运算符的类,在“t=u”后确保t == u,并且一个非const限定符的复制赋值运算符不保证后置条件。这样,“const T”就可以被分配,但“T”不能。我无法想象任何有效的用例,如果我看到这样的代码,我可能会哭。 - James McNellis
由于标准定义了“可分配(assignable)”,因此后置条件是t和u等效。顺便问一下,C++0x是否会删除可赋值性的要求?我在草案中没有看到它了,不需要分配的操作应该可以工作。 - UncleBens
@UncleBens:在C++0x中,可分配和可复制构造的概念要求已被一组概念所取代,包括MoveInsertable、CopyInsertable和EmplaceConstructible,并且这些要求不再适用于整个容器,而是适用于容器上的单个操作(因为某些操作只能在对象可复制时才能工作,但其他操作则可以在对象仅可移动时工作)。 - James McNellis

1

这根本不应该起作用。在§23.1 ¶ 3中,正如您所说,规定存储在容器中的对象必须是CopyConstructible(如§20.1.3中所指定)和Assignable

类型TAssignable要求是,对于类型Ttu,您可以执行以下操作:

t = u

在程序中,返回值为T&,并且t等于u的后置条件(§23.1 ¶4)。

因此,明显const类型不可被赋值,因为执行t = u将引发编译错误(§7.1.5.1 ¶5)。

我认为这是Microsoft实现中的一个漏洞。在Linux上,g++如果您尝试实例化vector<const int>,则会发出典型的、25万亿行的模板错误(测试了带有和不带有-std=c++0x标志的情况,以防万一)(§23.1 ¶4)。

顺便说一下,这也在IBM FAQ中详细说明。


理论上,正如@James McNellis所说,编译器不需要在向量实例化时崩溃(如果它是未定义的行为,则任何事情都可能发生-包括一切正常工作);但是,在赋值语句中存在违反标准的情况,应该产生编译错误。

事实上,operator[]成员返回一个vector<const int>::reference;这要求它是T的左值(§23.1 ¶5表66);由于Tconst类型,它将是一个const左值。因此,我们降到了(§7.1.5.1 ¶5),它定义了试图对const元素执行赋值的代码为“不良形式”,这要求产生编译错误或至少警告,因为赋值给const是可诊断的规则(§1.4 ¶1-2)(没有指定“不需要诊断”的声明)。


最终编辑

实际上,@James McNellis是正确的;一旦您通过实例化vector<const int>来调用未定义行为,通常的规则就不再有价值了,因此无论它做什么 - 包括从元素类型中删除const或生成通常的鼻部恶魔,实现仍然符合标准。


1
更简单的方法是使用remove_const类型特征(这个确切的特征应该在0x中,但可能有不同的名称),而不是可变的。 - Fred Nurk
@Fred Nurk:很有可能就像你说的那样。实际上,我认为我之前提到的mutable是个错误的想法,因为mutable是用于非const(§7.1.2 ¶8)类字段的,让它们可以被const方法修改,与从模板类型中移除const无关。 - Matteo Italia
这不是实现中的错误。OP的程序表现出未定义的行为。 - James McNellis
@James McNellis:正确,vector 实例化是未定义行为并且不会产生错误,但是对 const 引用的赋值会产生错误。 - Matteo Italia
1
@Matteo:不,它没有。如果您使用不符合可复制和可分配要求的类型实例化它,则对于vector的行为没有任何要求。 - James McNellis
@James McNellis:嗯,这很有道理,一旦您调用了UB,标准规则就不再有价值了。我改正了,给你+1。 - Matteo Italia

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