for循环里如果std::pair的类型写不对,可能会产生拷贝初始化行为,造成性能损失。以及推荐写法。

第一版

    std::map<int, int> t;
    t.emplace(1, 1);
    



    for (const std::pair<int,int>& data : t)
    {
        int i = 0;
        std::ignore = i;
    }



中间留一些空格,是因为ms在调试的时候,
尤其是模板比较多的时候,经常断点的行号有问题。
比如第5行的断点,需要打在第4行。
如果代码之间没空行,经常断点会搞错。

把断点下在:for (const std::pair<int,int>& data : t)

虽然是用了const,而且还是引用。

但发现居然调用了构造函数,既然调用了构造函数,那么就存在拷贝的可能性,这里是int所以没关系,如果first和second是class或struct类型,就会发生拷贝动作,对std::pair进行了拷贝初始化:

所调用的std::pair所在的文件位置:

D:\DevTools\VS2017\VC\Tools\MSVC\14.16.27023\include\utility

	template<class _Other1,
		class _Other2,
		enable_if_t<conjunction_v<
			is_constructible<_Ty1, const _Other1&>,
			is_constructible<_Ty2, const _Other2&>,
			is_convertible<const _Other1&, _Ty1>,
			is_convertible<const _Other2&, _Ty2>
		>, int> = 0>
		constexpr pair(const pair<_Other1, _Other2>& _Right)
			_NOEXCEPT_COND(is_nothrow_constructible_v<_Ty1, const _Other1&>
				&& is_nothrow_constructible_v<_Ty2, const _Other2&>)	// strengthened
		: first(_Right.first), second(_Right.second)
		{
		}

看其堆栈:

把文本给拷贝出来,可以看清楚:是用std::pair<int const,int>对std::pair<int,int>类型的对象进行了拷贝初始化,first和second都在做拷贝。

其中,std::pair<int const,int>是编译器推导出来的类型,std::pair<int,int>是for循环里显式指定的类型:

std::pair<int,int>::pair<int,int><int const ,int,0>
(const std::pair<int const ,int> & _Right={...}

)

这个pair构造函数也是个模板,所以上面的<int const ,int,0>就是pair构造函数的参数的推导结果。

template<class _Ty1,
	class _Ty2>
	struct pair
	{
...........

	template<class _Other1,
		class _Other2,
		enable_if_t<conjunction_v<
			is_constructible<_Ty1, const _Other1&>,
			is_constructible<_Ty2, const _Other2&>,
			is_convertible<const _Other1&, _Ty1>,
			is_convertible<const _Other2&, _Ty2>
		>, int> = 0>
		constexpr pair(const pair<_Other1, _Other2>& _Right)
			_NOEXCEPT_COND(is_nothrow_constructible_v<_Ty1, const _Other1&>
				&& is_nothrow_constructible_v<_Ty2, const _Other2&>)	// strengthened
		: first(_Right.first), second(_Right.second)
		{
		}

...........
};

1、其中,std::pair<int,int>::pair<int,int>就是构造函数,就是for (const std::pair<int,int>& data : t)里的std::pair<int,int>& data,显式指定的类型。

2、<int const ,int,0>是编译器推导出来的形参类型,因为构造函数也是模板函数,参数类型也要推导出来。0就是enable_if语句出来的结果。

3、const std::pair<int const ,int>就是根据实参推导出来的类型

思考:

由于构造函数也是个模板,所以它的参数也是推导出来的,参数的类型是const std::pair<int const ,int> & _Right={...}

也就是说推导出来的key的类型是int const,value的类型是int,那么它所对应的std::pair应该是std::pair<int const,int>。而for循环里显式指定了std::pair<int,int>,所以这里面就产生了一个拷贝构造函数的动作。可以认为const std::pair<int,int>是一个临时对象?

改个写法,key上添加const

    std::map<int, int> t;
    //t.emplace(1, 1);
    for (const std::pair<const int,int>& data : t)
    {
        auto x = std::make_unique<int>();
    }

这个时候,就不会走pair的构造函数了,也变相说明,和编译器推导出的类型保持一致了。

打印出来看看吧

template<typename T>
struct TypePrinter;


void main()
{
    std::map<int, int> t;
    t.emplace(1, 1);

    for (auto& data : t)
    {
        TypePrinter<decltype(data)> a;
    }

}

编译结果:
“a”使用未定义的 struct“TypePrinter<std::pair<const _Kty,_Ty> &>”

可以看出,key就是const的

结论

最好的办法是用for(const auto& data : t),这种写法也不会走std::pair的构造函数。这种比较保险,也省事,因为是它推导出来的类型。

这篇文章的目的,是更深入理解编译器在推导时所搞出来的一些隐藏的“坑”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值