C++11允许使用vector<const T>吗?

104

从C++03到C++11,容器的要求发生了变化。C++03有一般性的要求(例如对于向量需要复制构造和赋值构造),而C++11对每个容器操作定义了细粒度的要求(第23.2部分)。

因此,您现在可以将一种可复制构造但不可赋值的类型存储在vector中,例如具有const成员的结构体,只要您仅执行不需要赋值的某些操作(例如构造和push_back)。但是,insert操作则不行。

我的疑问是:这是否意味着标准现在允许vector<const T>?我没有看到任何不允许的理由- const T就像具有const成员的结构体一样,是可复制构造但不可赋值的类型- 但我可能错过了什么。

(让我怀疑自己是否错过了什么的部分是:如果您尝试实例化vector<const T>,gcc trunk会崩溃,但是对于具有const成员的T,vector<T>可以正常工作。)


Clang的标准库允许vector<const T>:https://gcc.godbolt.org/z/71PPjcxsa - Fedor
5个回答

74
不,我相信分配器要求T可以是“非const、非引用对象类型”。一个常量对象的向量没什么用处。而且一个const vector也几乎一样。
多年后,这个快而脏的答案仍然吸引着评论和投票,但并非总是如此。 :-)
因此,为了添加一些适当的参考文献:
对于我手头上的C++03标准,在[lib.allocator.requirements]部分的表31中说:
T,U 任何类型
并不是所有类型都可以正常工作。
因此,下一个标准C++11在[allocator.requirements]中在一个近期的草案中,现在的表27中说:
T,U,C 任何非const、非引用对象类型
这与我最初从记忆中写的非常接近。这也是问题所在。
然而,在C++14(draft N4296)中,表27现在说:
T,U,C 任何非const对象类型
可能是因为引用实际上并不是对象类型?
现在在C++17(draft N4659)中,它是表30说:
T,U,C 任何cv-unqualified对象类型(6.9)

因此不仅排除了const,还排除了volatile。可能是旧闻,只是澄清一下。


请查看Howard Hinnant的第一手信息, 目前就在下面。


48
底线是:我们并没有设计容器来保存const T。虽然我曾经考虑过这个问题,而且我们差点意外地做到了。据我所知,目前的难点在于默认分配器中两个重载的address成员函数:当T为const时,这两个重载具有相同的签名。纠正这个问题的简单方法是专门化std::allocator<const T>并删除其中一个重载。 - Howard Hinnant
1
@Howard:这是否意味着应该允许vector<T, my_const_friendly_allocator<T>> - HighCommander4
5
@HighCommander4:我不确定。在libc++上,我可以使用协作式分配器构建一个向量(即使是非空的)。但我不能对它做其他事情(非const)。我不确定这是否符合你对“工作”的定义。我也不确定我是否无意中利用了某个扩展。要确信这一点,我需要花更多时间研究这个问题。我曾经投入过这样的时间,但那是几年前的事情,期间发生了很多变化。如果它确实有效,那并不是委员会有意设计的。 - Howard Hinnant
9
缓存通常是可变容器中的不可变对象,而排序向量通常是地图的替代方案,因此我不同意认为带有const对象的向量没有多少用处。 - Chris Oldwood
14
很遗憾,我之前一直在使用 std::vector<const T>,因为它非常类似于 const std::vector<T>,但对于保存它的类没有后者那么负面的影响。实际上,在我大多数使用 vector 的情况下,语义上我需要的正是 std::vector<const T>。现在我不得不放弃 const,也就失去了它所提供的可靠性。 - Violet Giraffe
显示剩余6条评论

37

更新

在我在2011年发表的被接受的(并且正确的)答案下评论:

底线:我们没有设计容器来容纳const T。虽然我考虑过这个问题。而且我们差点意外地做到了。据我所知,目前的瓶颈是默认分配器中一对重载的address成员函数:Tconst时,这两个重载具有相同的签名。纠正这个问题的简单方法是专门化std::allocator<const T>并删除其中一个重载。

随着即将推出的C++17草案,我认为现在我们已经合法化了vector<const T>,而且我也相信这是意外发生的。:-)

P0174R0std::allocator<T>中删除了address重载。 P0174R0在其理由中没有提到支持std::allocator<const T>

更正

在下面的评论中,T.C.正确地指出了address重载是已弃用,而不是被删除。我犯了错误。在定义std::allocator的20.10.9中,这些已弃用的成员不会显示出来,而是被降级到D.9节。当我发布这篇文章时,我忽略了扫描第D章可能出现这种情况的可能性。

感谢T.C.的更正。我考虑删除这个误导性的答案,但或许最好将其保留并进行更正,以便其他人不会像我一样误读规范。


9
这真是有趣!(现在我们只需要保持安静,并且让它悄悄地进入C++17,让任何人都没注意到 :) ) - HighCommander4
3
“分配器需求表”是否仍然完全禁止它?不管怎样,“P0174R2”(被投票通过的修订版)只是废弃而不是移除“address”。 - T.C.
@T.C.:你说得完全正确。感谢纠正。 - Howard Hinnant
所以c++2x最终将允许vector<const T> :) - M.M
1
答案“底线:我们没有设计容器来保存const T。”假设目标是容器应该保存“const T”。 然而,有人可能会认为用户的目标是限制对容器的操作 - 以便例如'back()'返回“const T&” - 不管容器包含什么。 - Hans Olsson

15
尽管我们已经有了非常好的答案,但我决定贡献一个更实用的答案,展示什么可以做什么不可行。
所以这个不起作用:
vector<const T> vec; 

阅读其他答案就可以明白为什么。正如你所猜测的那样,这也不起作用:

vector<const shared_ptr<T>> vec;

T 不再是 const,但 vector 持有的是 shared_ptr,而不是 T

另一方面,这个做法是可行的:

vector<const T *> vec;
vector<T const *> vec;  // the same as above

但在这种情况下,const是被指向的对象,而不是指针本身(这是向量存储的内容)。这相当于:

vector<shared_ptr<const T>> vec;

这很好。

但是,如果我们在表达式的末尾加上 const ,它会将指针转换为一个 const,因此以下代码将无法编译:

vector<T * const> vec;

有点令人困惑,我同意,但你会逐渐习惯的。


如果我将一个"const vector<const vector<string>>& v"作为函数的输入参数,我该如何修改它以使其能够正确编译?是否需要将其改为"const vector<const vector<string> *>& v"? - undefined

7

补充其他答案,另一种方法是使用:

vector<unique_ptr<const T>> vec;

如果你想强制只有vec拥有它的项目,或者想要将项目动态移入vec并在某个时刻将其移出,则可以使用指针const语义可能会令人困惑,但shared_ptrunique_ptr则不会。 const unique_ptr<T>是一个常量指针,unique_ptr<const T>是一个常量指向,正如您所期望的那样。


-2
据我所知,如果您想让您的向量中的每个T元素都是const,只需使用const vector即可。因为如果您的向量被const修饰,只有那些不会修改任何T元素的const方法才能被调用。

2
这将不允许向向量添加、删除或重新排序元素。 - bolov
当然,向向量添加、删除、重新排序方法是非const限定的。我真正想表达的是,如果你想将元素视为const,只需使向量具有const限定即可。 - pigbrother
我的观点是它可以做更多的事情,但也更具限制性。 - bolov
@bolov 好的,非常感谢。 - pigbrother

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