使用foreach遍历map时出现意外的副本

17

我试图循环遍历一个映射表,但我得到了意外的副本。这是程序:

#include <iostream>
#include <map>
#include <string>

struct X
{
    X()
    {
        std::cout << "default constructor\n";
    }

    X(const X&)
    {
        std::cout << "copy constructor\n";
    }
};

int main()
{
    std::map<int, X> numbers = {{1, X()}, {2, X()}, {3, X()}};
    std::cout << "STARTING LOOP\n";
    for (const std::pair<int, X>& p : numbers)
    {
    }
    std::cout << "ENDING LOOP\n";
}

这里是输出结果:

default constructor
copy constructor
default constructor
copy constructor
default constructor
copy constructor
copy constructor
copy constructor
copy constructor
STARTING LOOP
copy constructor
copy constructor
copy constructor
ENDING LOOP

为什么在循环内部我会得到三个复制品?如果使用类型推断,这些复制品会消失:

for (auto&& p : numbers)
{
}

这里发生了什么事?

3个回答

16

map<K,V> 的值类型是 pair<const K,V>; 所以您的循环需要将 pair<const int,X> 转换为 pair<int,X>,同时复制键和值,以给您引用该类型的内容。

使用正确的类型(显式指定或使用 auto 推导)将消除副本。


理论上,语言是否允许将const pair<K, V>&绑定到pair<const K, V>?或者这会在常量正确性系统中留下漏洞吗?如果允许这样做,我会不会破坏任何东西? - fredoverflow
@FredOverflow:这会改变调用的方法(在const限定和非限定之间),这让我感到不安。 - Matthieu M.
@FredOverflow:还有一个问题,用于实例化模板的类型的属性不会传播到实例化的模板。std::pair<X,Y>std::pair<T,U>无关,除非X恰好是T,而Y恰好是U。你请求的绑定支持不能在语言层面上完成,因为它取决于模板的实现,可能会有一些专门针对const类型的模板特化,在这种情况下,T<U>的真实类型和占用空间可能完全不同于T<const U> - David Rodríguez - dribeas

5
std::map<K, V> 中,value_type 不是 std::pair<K, V> 而是 std::pair<K const, V>。也就是说,您不能更改元素的关键组件。如果编译器发现从 std::pair<K const, K> const& 获取 std::pair<K, V> const& 的请求,它将使用 std::pair<...> 的转换构造函数,并创建一个合适的临时对象。
我建议使用 value_type 来避免任何差异:
for (std::map<int, X>::value_type const& p: numbers)
    ...

或者你可以让类型被推断出来:

for (auto const& p: number)
    ...

理论上,语言是否允许将const pair<K, V>&绑定到pair<const K, V>?或者这会在常量正确性系统中留下漏洞吗?如果允许这样做,我会不会破坏任何东西? - fredoverflow
const pair<K, V>& 转换为 pair<const K, V> 会破坏 V。如果稍后标记K为可变,则将 pair<const K, V> 转换为 const pair<K, V>& 可能会破坏K。 - leewz
1
@FredOverflow:编译器不知道模板参数的使用方式。使用不同类型进行实例化被视为完全不同的类型。 - Dietmar Kühl
如果有解决问题的方法,那就是禁止隐式转换。 - Dietmar Kühl

4
由于您正在迭代映射,但绑定了错误的引用类型,因此会产生复制。该映射的value_typestd::pair<const int, X>(请注意const)。由于它们不是相同的类型,编译器会创建临时变量并绑定引用。
在大多数情况下,您可以(并且应该)使用auto&const auto&进行迭代,这将避免此类问题。如果要拼写类型,可以使用嵌套的value_type或精确类型。
for (const std::map<int,X>::value_type& r : numbers) {
// or
for (const std::pair<const int,X>& r : numbers) {

偏好应该是:const auto& r 然后是 const std::map<...>::value_type& 接着是 const std::pair<const int, X>&。请注意,列表中越靠右,您提供的知识就越多,编译器就能帮助您的就越少。

1
从理论上讲,语言是否允许将const pair<K, V>&绑定到pair<const K, V>?或者这是否会在const正确性系统中留下漏洞?如果允许,我会不会破坏任何东西? - fredoverflow
std::pair的隐式转换非常糟糕 +1。 - user2249683
@FredOverflow:已经在您上面发表的评论中回答了。简单回答:不,这不能在语言层面上完成。 - David Rodríguez - dribeas

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